依赖注入与工厂模式

时间:2009-02-17 17:03:45

标签: dependency-injection factory-pattern design-patterns

使用依赖注入的大多数示例引用,我们也可以使用工厂模式解决。看起来在使用/设计时,依赖注入和工厂之间的差异是模糊的还是薄的。

有人告诉我,你如何使用它会有所作为!

我曾经使用StructureMap DI容器来解决问题,稍后我重新设计它以使用简单的工厂并删除对StructureMap的引用。

任何人都可以告诉我他们之间有什么区别,在哪里使用什么,这里最好的做法是什么?

28 个答案:

答案 0 :(得分:280)

使用工厂时,您的代码仍然负责创建对象。通过DI,您将该职责外包给另一个类或框架,该框架或框架与您的代码分开。

答案 1 :(得分:210)

我建议保持概念简单明了。依赖注入更像是松散耦合软件组件的架构模式。工厂模式只是将创建其他类对象的责任分离到另一个实体的一种方法。工厂模式可以被称为实现DI的工具。依赖注入可以通过许多方式实现,例如DI使用构造函数,使用映射xml文件等。

答案 2 :(得分:168)

依赖注入

而不是实例化零件本身,汽车询问以获取它需要运行的零件。

class Car
{
    private Engine engine;
    private SteeringWheel wheel;
    private Tires tires;

    public Car(Engine engine, SteeringWheel wheel, Tires tires)
    {
        this.engine = engine;
        this.wheel = wheel;
        this.tires = tires;
    }
}

<强>工厂

将碎片放在一起以制作完整的物体,并隐藏来自呼叫者的具体类型。

static class CarFactory
{
    public ICar BuildCar()
    {
        Engine engine = new Engine();
        SteeringWheel steeringWheel = new SteeringWheel();
        Tires tires = new Tires();
        ICar car = new RaceCar(engine, steeringWheel, tires);
        return car;
    }   
}

<强>结果

如您所见,工厂与DI相辅相成。

static void Main()
{
     ICar car = CarFactory.BuildCar();
     // use car
}

你还记得金发姑娘和三只熊吗?那么,依赖注入有点像那样。这有三种方法可以做同样的事情。

void RaceCar() // example #1
{
    ICar car = CarFactory.BuildCar();
    car.Race();
}

void RaceCar(ICarFactory carFactory) // example #2
{
    ICar car = carFactory.BuildCar();
    car.Race();
}

void RaceCar(ICar car) // example #3
{
    car.Race();
}

示例#1 - 这是最糟糕的,因为它完全隐藏了依赖关系。如果你把这个方法看成一个黑盒子,你就不知道它需要一辆车。

示例#2 - 这有点好,因为现在我们知道自从我们进入汽车工厂后我们需要一辆汽车。但这次我们传球太多,因为所有方法实际需要的都是汽车。当汽车可以在方法之外建造并通过时,我们正在通过工厂来建造汽车。

示例#3 - 这是理想的,因为该方法要求完全它需要什么。不是太多或太少。我不必为了创建MockCars而编写一个MockCarFactory,我可以直接传递模拟。它是直接的,界面不会说谎。

Misko Hevery的Google Tech Talk非常棒,是我从中得到的例子的基础。 http://www.youtube.com/watch?v=XcT4yYu_TTs

答案 3 :(得分:39)

有一些问题很容易通过依赖注入来解决,而这些问题并不是很容易用一套工厂解决的。

一方面,控制和依赖注入(IOC / DI)的反转,另一方面,服务定位器或一组工厂(工厂)之间的一些区别是:

IOC / DI是一个完整的域对象和服务生态系统。它以您指定的方式为您设置所有内容。您的域对象和服务由容器构造,并且不构造自身:因此它们在容器或任何工厂上没有任何依赖关系。 IOC / DI允许极高的可配置性,所有配置都位于应用程序最顶层(GUI,Web前端)的单个位置(容器的构造)。

工厂抽象出你的域对象和服务的一些构造。但是域对象和服务仍然负责确定如何构建自己以及如何获得他们所依赖的所有东西。所有这些“活动”依赖项都会过滤应用程序中的所有层。没有一个地方可以配置所有东西。

答案 4 :(得分:36)

依赖注入(DI)和工厂模式相似的原因是因为它们是控制反转(IoC)的两种实现,它是一种软件体系结构。简单地说,它们是解决同一问题的两种方法。

因此,为了回答这个问题,工厂模式和DI之间的主要区别在于如何获得对象引用。使用依赖注入意味着引用被注入或提供给您的代码。使用Factory模式,您的代码必须请求引用,以便您的代码获取对象。这两个实现都删除或解耦代码与代码使用的对象引用的基础类或类型之间的链接。

值得注意的是,可以编写工厂模式(或实际上是返回返回对象引用的新工厂的工厂的抽象工厂模式),以动态选择或链接到运行时请求的对象的类型或类。这使它们与服务定位器模式非常相似(甚至比DI更多),这是IoC的另一种实现。

工厂设计模式相当陈旧(就软件而言)并且已经存在了一段时间。自最近建筑模式IoC的普及以来,它正在复苏。

我想当谈到IoC设计模式时:注入器正在注入,定位器正在定位,工厂已被重构。

答案 5 :(得分:24)

DI的一个缺点是它不能用逻辑初始化对象。例如,当我需要创建一个具有随机名称和年龄的角色时,DI不是工厂模式的选择。通过工厂,我们可以轻松地从对象创建中封装随机算法,该算法支持一种名为“封装变化的”设计模式。

答案 6 :(得分:21)

  

生命周期管理是依赖性容器除了实例化和注入之外所承担的责任之一。容器有时在实例化后保留对组件的引用这一事实是它被称为“容器”而不是工厂的原因。依赖注入容器通常只保留对管理生命周期所需的对象的引用,或者为将来注入而重用的对象,如单例或flyweights。当配置为每次调用容器创建一些组件的新实例时,容器通常只会忘记创建的对象。

来自:http://tutorials.jenkov.com/dependency-injection/dependency-injection-containers.html

答案 7 :(得分:16)

我认为DI是工厂的一种抽象层,但它们也提供了超越抽象的好处。真正的工厂知道如何实例化单个类型并对其进行配置。良好的DI层通过配置提供实例化和配置多种类型的能力。

显然,对于一个简单类型的项目,在构造中需要相对稳定的业务逻辑,工厂模式易于理解,实现并且运行良好。

OTOH,如果您的项目包含许多类型,您希望经常更改它们的实现,DI可以通过其配置灵活地在运行时执行此操作,而无需重新编译工厂。

答案 8 :(得分:12)

理论

有两点需要考虑:

  1. 谁创建对象

    • [工厂]:你必须写HOW对象应该被创建。您有单独的Factory类,其中包含创建逻辑。
    • [依赖注入]:在实际情况下由外部框架完成(例如在Java中将是spring / ejb / guice)。注射发生&#34;神奇地&#34;没有明确地创建新对象
  2. 它管理的是什么类型的对象:

    • [工厂]:通常负责创建有状态对象
    • [依赖注入]更有可能创建无状态对象
  3. 如何在单个项目中使用工厂和依赖注入的实际示例

    1. 我们想要建立什么
    2. 用于创建订单的应用程序模块,其中包含多个名为orderline的条目。

      1. 建筑
      2. 假设我们想要创建以下层架构:

        enter image description here

        域对象可以是存储在数据库中的对象。 存储库(DAO)帮助从数据库中检索对象。 Service为其他模块提供API。在order模块

        上进行操作
        1. 域层和工厂用法
        2. 将在数据库中的实体是Order和OrderLine。订单可以有多个OrderLines。 Relationship between Order and OrderLine

          现在是重要的设计部分。这个模块之外的模块是否应该自己创建和管理OrderLines?编号订单行只有在与订单关联时才存在。如果你可以将内部实现隐藏到外部类中,那将是最好的。

          但是如何在不了解OrderLines的情况下创建Order?

          想要创建新订单的人使用OrderFactory(这将隐藏有关我们如何创建订单的详细信息)。

          enter image description here

          这就是IDE内部的样子。 domain包之外的类将使用OrderFactory而不是Order内的构造函数

          1. 依赖注入 依赖注入更常用于无状态层,例如存储库和服务。
          2. OrderRepository和OrderService由依赖注入框架管理。 存储库负责管理数据库上的CRUD操作。 Service注入Repository并使用它来保存/查找正确的域类。

            enter image description here

答案 9 :(得分:12)

我知道这个问题已经过时了,但我想加上我的五美分,

我认为依赖注入(DI)在许多方面都像可配置的工厂模式(FP),从这个意义上来说,你可以用DI做的任何东西都可以用这样的工厂来做。

实际上,如果您使用spring,例如,您可以选择自动装配资源(DI)或执行以下操作:

MyBean mb = ctx.getBean("myBean");

然后使用'mb'实例做任何事情。这不是一个工厂的调用会返回你的实例?

我注意到大多数FP示例之间唯一真正的区别是你可以在xml或另一个类中配置“myBean”,并且框架将作为工厂工作,但除此之外是同样的事情,你可以拥有一个工厂,它可以读取配置文件或获得所需的实现。

如果你问我的意见(而且我知道你没有),我相信DI会做同样的事情,但只会增加开发的复杂性,为什么?

好吧,首先,要知道你使用DI自动装配的任何bean的实现是什么,你必须自己去配置。

但是......那个你不必知道你正在使用的对象的实现的承诺怎么样? pfft!当真?当你使用这样的方法时...你是不是写了实现?即使你不这样做,你几乎不是一直在看实现如何做它应该做的事情?

并且最后一点, DI框架承诺你有多少与你建立解耦并没有依赖于他们的类如果你正在使用一个框架,你可以构建它所有的东西,如果你必须改变方法或框架,这将不是一件容易的事......永远! ...但是,自从你建立起来围绕这个特定框架的一切,而不是担心什么是最适合您业务的解决方案,那么在这样做时您将面临一个双重问题。

事实上,我能看到的FP或DI方法的唯一真正的业务应用是你需要改变运行时所使用的实现,但至少我知道的框架没有允许你这样做,如果你需要使用另一种方法,你必须在开发时将一切都完美地保留在配置中。

所以,如果我有一个在同一个应用程序中的两个作用域中执行不同的类(比如两个持有的公司),我必须配置框架来创建两个不同的bean,并调整我的代码以使用它们。是不是就像我写这样的东西一样:

MyBean mb = MyBeanForEntreprise1(); //In the classes of the first enterprise
MyBean mb = MyBeanForEntreprise2(); //In the classes of the second enterprise

与此相同:

@Autowired MyBean mbForEnterprise1; //In the classes of the first enterprise
@Autowired MyBean mbForEnterprise2; //In the classes of the second enterprise

而且:

MyBean mb = (MyBean)MyFactory.get("myBeanForEntreprise1"); //In the classes of the first enterprise
MyBean mb = (MyBean)MyFactory.get("myBeanForEntreprise2"); //In the classes of the second enterprise

在任何情况下,您都必须更改应用程序中的某些内容,无论是类还是配置文件,但您必须重新部署它。

做这样的事情不是很好吗:

MyBean mb = (MyBean)MyFactory.get("mb"); 

这样,您可以设置工厂代码以在运行时获得正确的实现,具体取决于已记录的用户企业?现在这会有所帮助。您可以添加一个包含新类的新jar,甚至可以在运行时设置规则(如果您打开此选项,则添加新的配置文件),不对现有类进行更改。这将是一个动态工厂!

这不比为每个企业编写两个配置更有帮助,甚至可能每个企业都有两个不同的应用程序吗?

你可以告诉我,我不需要在运行时进行切换,所以我配置了应用程序,如果我继承该类或使用其他实现,我只需更改配置并重新部署。好的,这也可以通过工厂完成。说实话,你这次做了多少次?也许只有当你的应用程序将在你公司的其他地方使用时,你才会将代码传递给另一个团队,他们会做这样的事情。但是,嘿,这也可以在工厂完成,并且在动态工厂中会更好!!

无论如何,评论部分如果你打开就杀了我。

答案 10 :(得分:6)

IOC是一个通过两种方式实现的概念。依赖项创建和依赖项注入,Factory / Abstract工厂是依赖项创建的示例。依赖注入是构造函数,设置器和接口。 IOC的核心是不依赖于具体的类,而是定义方法的抽象(比如接口/抽象类)并使用该抽象来调用具体类的方法。像Factory模式一样返回基类或接口。 Similariliy依赖注入使用基类/接口来设置对象的值。

答案 11 :(得分:5)

通过依赖注入,客户端不需要自己获取依赖项,而是事先准备好了。

对于工厂,有人必须调用这些工具才能将生成的对象转移到需要它们的地方。

差别主要在于这一行调用工厂并获取构造的对象。

但是对于工厂,你必须在任何地方写这一行你需要这样一个对象。使用DI,您只需创建一次布线(使用和创建对象之间的关系),然后再依赖于对象的存在。另一方面,DI通常需要更多(取决于框架)在准备方面的工作量。

答案 12 :(得分:3)

您可以查看this link,了解真实示例中两种(和其他)方法的比较。

基本上,当需求发生变化时,如果使用工厂而不是DI,最终会修改更多代码。

这对手动DI也有效(即,没有外部框架提供对象的依赖关系,但是你在每个构造函数中传递它们。)

答案 13 :(得分:3)

我在读到DI时就遇到了同样的问题,最后在这篇文章中结束了。 所以最后这是我理解的,但如果我错了,请纠正我。

“很久以前,根据自己的书面规则,很少有王国与自己的管理机构控制和作出决定。后来成立了一个大政府,取消所有这些有一套规则(宪法)的小理事机构并实施通过法院“

小王国的管理机构是“工厂”

大政府是“依赖注射者”。

答案 14 :(得分:2)

Binoj,

我认为你不必选择其中一个。

将依赖类或接口移动到类构造函数或setter的行为遵循DI模式。传递给构造函数或集合的对象可以使用Factory实现。

何时使用?使用开发人员驾驶室中的图案。他们觉得最舒服,最容易理解。

答案 15 :(得分:2)

我认为DI是一种配置或实例化bean的方式。 DI可以通过构造函数,setter-getter等多种方式完成。

工厂模式只是实例化bean的另一种方式。这个模式主要用于你必须使用工厂设计模式创建对象时使用,因为在使用这个模式时你不配置bean的属性,只实例化对象。

点击此链接:Dependency Injection

答案 16 :(得分:2)

我相信,有三个重要方面来管理对象及其用法:
1. 实例化(一个类,如果有的话,还有初始化) 2. 注入(如此创建的实例)在需要的地方 3. 生命周期管理(如此创建的实例)
使用工厂模式,实现了第一个方面(实例化),但剩下的两个方面是有问题的。使用其他实例的类必须对工厂进行硬编码(而不是创建的实例),这会阻碍松耦合能力。此外,实例的生命周期管理成为在多个地方使用工厂的大型应用程序中的挑战(特别是,如果工厂不管理它返回的实例的生命周期,它会得到丑陋)。

另一方面,使用DI(IoC模式),所有3个都在代码之外被抽象(到DI容器),并且托管bean不需要这种复杂性。 松散耦合,一个非常重要的建筑目标可以安静舒适地实现。另一个重要的架构目标是,关注点分离可以比工厂更好地实现。

虽然工厂可能适合小型应用,但大型工厂选择DI会更好。

答案 17 :(得分:1)

这里的大多数答案都解释了两者的概念差异和实现细节。但是我无法找到关于IMO最重要且OP询问的应用差异的解释。那么让我重新开启这个话题......

  

有人告诉我你如何使用它会产生影响!

完全。在90%的情况下,您可以使用Factory或DI获得对象引用,通常最终会使用后者。使用Factory的另外10%的情况是唯一正确的方式。这些情况包括在运行时参数通过变量获取对象。像这样:

IWebClient client = factoryWithCache.GetWebClient(url: "stackoverflow.com",
        useCookies: false, connectionTimeout: 120);

在这种情况下,从DI中获取client是不可能的(或者至少需要一些丑陋的解决方法)。因此,作为决策的一般规则:如果可以在没有任何运行时计算参数的情况下获得依赖性,则首选DI,否则使用Factory。

答案 18 :(得分:1)

注入框架是工厂模式的实现。

这完全取决于您的要求。如果您需要在应用程序中实现工厂模式,那么很有可能您的需求将通过其中的众多注入框架实现之一来满足。

如果任何第三方框架无法满足您的要求,您应该只推出自己的解决方案。您编写的代码越多,您需要维护的代码就越多。代码是一种负债而非资产。

关于应该使用哪种实现的争论并不像了解应用程序的架构需求那样重要。

答案 19 :(得分:1)

从面值看起来相同

简单来说,工厂模式,创建模式有助于创建一个对象 - “定义创建对象的接口”。如果我们有一个键值类型的对象池(例如Dictionary),将密钥传递给Factory(我指的是简单工厂模式),您可以解析Type。任务完成! 另一方面,依赖注入框架(例如Structure Map,Ninject,Unity等)似乎在做同样的事情。

但......“不要重新发明轮子”

从建筑的角度来看,它是一个绑定层和“不要重新发明轮子”。

对于企业级应用程序,DI的概念更像是一个定义依赖关系的架构层。为了进一步简化这一点,您可以将其视为一个单独的类库工程,它可以解决依赖关系。主应用程序依赖于此项目,其中依赖项解析程序引用其他具体实现和依赖项解析。

除了工厂的“GetType / Create”之外,我们通常需要更多功能(能够使用XML来定义依赖关系,模拟和单元测试等)。由于您引用了结构图,请查看Structure Map feature list。它显然不仅仅是简单地解析简单对象映射。不要重新发明轮子!

如果你拥有的只是一把锤子,那么一切看起来都像钉子

根据您的要求以及您构建的应用程序类型,您需要做出选择。如果它只有很少的项目(可能是一个或两个......)并且涉及很少的依赖项,那么你可以选择一种更简单的方法。这就像使用ADO .Net数据访问而不是使用Entity Framework进行简单的1或2个数据库调用一样,在这种情况下引入EF是一种过度杀伤。

但是对于一个更大的项目或者如果你的项目变得更大,我强烈建议有一个带有框架的DI层,并腾出空间来改变你使用的DI框架(在主应用程序中使用Facade(Web App,Web) Api,Desktop..etc。)。

答案 20 :(得分:1)

工厂设计模式

工厂设计模式的特点是

  • 界面
  • 实施班级
  • 工厂

当您质疑自己时,您可以观察到一些事情

  • 工厂何时为实现类创建对象 - 运行时或编译时间?
  • 如果要在运行时切换实现,该怎么办? - 不可能

这些由依赖注入处理。

依赖注入

您可以通过不同的方式注入依赖关系。为简单起见,请使用Interface Injection

在DI中,容器创建所需的实例,并将它们“注入”到对象中。

因此消除了静态实例化。

示例:

public class MyClass{

  MyInterface find= null;

  //Constructor- During the object instantiation

  public MyClass(MyInterface myInterface ) {

       find = myInterface ;
  }

  public void myMethod(){

       find.doSomething();

  }
}

答案 21 :(得分:1)

我的想法:

Dependecy Injection:将协作者作为参数传递给构造函数。 依赖注入框架:一个通用且可配置的工厂,用于创建要作为参数传递给构造函数的对象。

答案 22 :(得分:0)

简单来说,依赖注入与工厂方法分别意味着推拉动机制。

拉机制:类间接依赖于Factory方法,而Factory方法依赖于具体类。

推送机制:根组件可以在一个位置配置所有相关组件,从而促进高维护和松耦合。

使用Factory方法的责任仍然在于类(虽然是间接的)来创建新对象,其中依赖注入将责任外包(尽管以泄漏抽象为代价)

答案 23 :(得分:0)

我认为这些是正交的,可以一起使用。让我向您展示我最近在工作中遇到的一个例子:

我们在Java中使用Spring框架进行DI。单例类(Parent)必须实例化另一个类的新对象(Child),并且那些具有复杂的协作者:

@Component
class Parent {
    // ...
    @Autowired
    Parent(Dep1 dep1, Dep2 dep2, ..., DepN depN) {
        this.dep1 = dep1;
        this.dep2 = dep2;
    }

    void method(int p) {
        Child c = new Child(dep1, dep2, ..., depN, p);
        // ...
    }
}

在此示例中,Parent必须仅接收DepX个实例以将其传递给Child构造函数。问题:

  1. ParentChild的了解比
  2. 更多
  3. Parent有更多的合作者
  4. Child添加依赖项涉及更改Parent
  5. 这是我意识到Factory完全适合这里的时候:

    1. 隐藏Child类的所有参数,Parent
    2. 它封装了创建Child的知识,可以集中在DI配置中。
    3. 这是简化的Parent类和ChildFactory类:

      @Component
      class Parent {
          // ...
          @Autowired
          Parent(ChildFactory childFactory) {
              this.childFactory = childFactory;
          }
      
          void method(int p) {
              Child c = childFactory.newChild(p);
              // ...
          }
      }
      
      @Component
      class ChildFactory {
          // ...
          @Autowired
          Parent(Dep1 dep1, Dep2 dep2, ..., DepN depN) {
              this.dep1 = dep1;
              this.dep2 = dep2;
              // ...
              this.depN = depN;
          }
      
          Child newChild(int p) {
              return new Child(dep1, dep2, ..., depN, p);
          }
      }
      

答案 24 :(得分:0)

当您完全知道此时需要哪种类型的对象时,可以使用依赖项注入。在采用工厂模式的情况下,您只是将创建对象的过程委托给工厂,因为您不清楚所需的对象的确切类型。

答案 25 :(得分:0)

DI给您一个composition root,它是用于连接对象图的单个集中位置。这往往会使对象依赖性变得非常明确,因为对象精确地要求他们需要什么,而只有一个地方可以得到它。

成分根是关注点的清晰直接分离。注入的对象不应依赖于DI机制,无论是第三方容器还是DIY DI。 DI应该是不可见的。

工厂往往更分散。不同的对象使用不同的工厂,这些工厂代表了对象及其实际依赖关系之间的间接附加层。此附加层将其自身的依存关系添加到对象图。工厂不是看不见的。工厂是中间人。

由于这个原因,更新工厂存在更多问题:由于工厂是业务逻辑的依赖项,因此修改工厂会产生连锁反应。组合根不是业务逻辑的依赖项,因此可以单独对其进行修改。

GoF提到了更新抽象工厂的困难。答案here中引用了其中部分解释。与工厂进行DI对比与Is ServiceLocator an anti-pattern?

这个问题也有很多共同点

最终,可能会选择答案。但我认为这可以归结为工厂作为中间人。问题是该中间商是否通过增加附加价值来增加重量,而不仅仅是提供产品。因为如果没有中间人就能得到相同的产品,那为什么不把中间人砍掉呢?

图表有助于说明差异。 DI vs Factory

答案 26 :(得分:-1)

我同时使用两者来创建“控制反转”策略,该策略对于需要在我之后进行维护的开发人员而言更具可读性。

我使用Factory创建不同的Layer对象(业务,数据访问)。

setSupportActionBar(toolbar);

另一个开发人员将看到此情况,并且在创建业务层对象时,他会在BusinessFactory中查找,并且Intellisense为开发人员提供了所有可能的业务层创建。不必玩游戏,找到我要创建的界面。

此结构已经是控制反转。我不再负责创建特定对象。但是您仍然需要确保依赖注入能够轻松地进行更改。 创建您自己的自定义Dependency Injection很荒谬,因此我使用Unity。在CreateCarBusiness()中,我要求Unity解析属于该类的类及其生命周期。

所以我的代码工厂依赖注入结构为:

ICarBusiness carBusiness = BusinessFactory.CreateCarBusiness();

现在我都受益。对于我使用的对象范围,其他开发人员也更容易理解我的代码,而不是构造函数依赖注入,后者只是说在创建类时每个对象都可用。

在创建单元测试时,我用它来将数据库数据访问更改为自定义编码的数据访问层。我不希望我的单元测试与数据库,Web服务器,电子邮件服务器等通信。他们需要测试我的业务层,因为这就是智能所在。

答案 27 :(得分:-3)

如果您符合以下条件,我认为使用依赖注入要好得多: 1.在小分区中部署代码,因为它可以很好地处理一个大代码的解耦。 2.可测试性是DI可以使用的情况之一,因为您可以轻松地模拟非解耦对象。通过使用接口,您可以轻松地模拟和测试每个对象。 3.你可以同时修改程序的每个部分,而不需要编码它的其他部分,因为它松散地解耦。