依赖注入与工厂模式

问题描述 投票:461回答:27

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

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

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

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

dependency-injection factory-pattern design-patterns
27个回答
283
投票

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


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,甚至可以在运行时设置规则(如果您打开此选项,则添加新的配置文件),不对现有类进行更改。这将是一个动态工厂!

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

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

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


6
投票

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


5
投票

通过依赖注入,客户端不需要自己获取它的依赖关系,它们都是事先准备好的。

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

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

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


3
投票

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

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

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

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


3
投票

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

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

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


2
投票

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

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

检查此链接:Dependency Injection


2
投票

Binoj,

我不认为你必须选择一个而不是另一个。

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

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


2
投票

我相信,有三个重要方面可以控制对象及其用法: 1.实例化(类和初始化一起)。 2.在需要的地方注入(如此创建的实例)。 3.生命周期管理(如此创建的实例)。 使用工厂模式,实现了第一个方面(实例化),但剩下的两个方面是有问题的。使用其他实例的类必须对工厂进行硬编码(而不是创建实例),这会阻碍松耦合能力。此外,实例的生命周期管理在大型应用程序中成为挑战,其中工厂在多个地方使用(特别是,如果工厂不管理它返回的实例的生命周期,则会变得难看)。 另一方面,使用DI(IoC模式),所有3个都在代码之外被抽象(到DI容器),并且托管bean不需要这种复杂性。松耦合,一个非常重要的建筑目标可以安静舒适地实现。另一个重要的建筑目标,关注点的分离可以比工厂更好地实现。

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


1
投票

我的想法:

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


1
投票

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

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

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

您应该使用哪些实现的参数并不像了解应用程序的体系结构需求那样重要。


211
投票

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


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();

  }
}

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,桌面) ..等等。)。


1
投票

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

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

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

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

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


1
投票

使用工厂,您可以对相关接口进行分组,因此如果传递的参数可以在工厂中分组,那么它也是constructor overinjection的一个很好的解决方案,请查看此代码*):

public AddressModelFactory(IAddressAttributeService addressAttributeService,
        IAddressAttributeParser addressAttributeParser,
        ILocalizationService localizationService,
        IStateProvinceService stateProvinceService,
        IAddressAttributeFormatter addressAttributeFormatter)
    {
        this._addressAttributeService = addressAttributeService;
        this._addressAttributeParser = addressAttributeParser;
        this._localizationService = localizationService;
        this._stateProvinceService = stateProvinceService;
        this._addressAttributeFormatter = addressAttributeFormatter;
    }

看一下构造函数,你只需要在那里传递IAddressModelFactory,所以参数更少*):

 public CustomerController(IAddressModelFactory addressModelFactory,
        ICustomerModelFactory customerModelFactory,
        IAuthenticationService authenticationService,
        DateTimeSettings dateTimeSettings,
        TaxSettings taxSettings,
        ILocalizationService localizationService,
        IWorkContext workContext,
        IStoreContext storeContext,
        ICustomerService customerService,
        ICustomerAttributeParser customerAttributeParser,
        ICustomerAttributeService customerAttributeService,
        IGenericAttributeService genericAttributeService,
        ICustomerRegistrationService customerRegistrationService,
        ITaxService taxService,
        CustomerSettings customerSettings,
        AddressSettings addressSettings,...

你在CustomerController看到很多参数传递,是的,你可以看到这是constructor overinjection,但这就是DI的工作原理。而且CustomerController没有任何问题。

*)代码来自nopCommerce。


1
投票

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

Pull机制:类间接依赖于Factory Method,而Factory Method又依赖于具体类。

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

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


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. Parent有更多的合作者
  3. Child添加依赖项涉及更改Parent

这是我意识到Factory完全适合这里的时候:

  1. 它隐藏了Child类的所有真正参数,如Parent所见
  2. 它封装了创建Child的知识,可以集中在DI配置中。

这是简化的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);
    }
}

-1
投票

我使用它们来创建一个Inversion of Control策略,为需要在我之后维护它的开发人员提供更多的可读性。

我使用Factory来创建不同的Layer对象(Business,Data Access)。

ICarBusiness carBusiness = BusinessFactory.CreateCarBusiness();

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

这种结构已经是反转控制。我不再负责创建特定对象。但是你仍然需要确保依赖注入能够轻松地改变事物。创建自己的自定义依赖注入是荒谬的,所以我使用Unity。在CreateCarBusiness()中,我要求Unity解决哪个类属于这个类并且它的生命周期。

所以我的代码Factory Dependency Injection结构是:

public static class BusinessFactory
{
    public static ICarBusiness CreateCarBusiness()
    {
       return Container.Resolve<ICarBusiness>();
    }
}

现在我有两个好处。对于其他开发人员来说,我的代码对于我使用的对象的范围也更具可读性,而不是构造函数依赖注入,它只是说在创建类时每个对象都可用。

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


-3
投票

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


172
投票

依赖注入

汽车不是要实例化部件本身,而是要求它运行所需的部件。

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


40
投票

存在易于通过依赖注入解决的问题,这些问题不是通过一组工厂很容易解决的。

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

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

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


40
投票

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

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

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

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

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


25
投票

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


22
投票

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

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


16
投票

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

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

OTOH,如果你有一个包含许多类型的项目,你希望它们的实现经常发生变化,那么通过它的配置,DI可以让你灵活地在运行时完成这个,而无需重新编译你的工厂。


13
投票

理论

有两个要点需要考虑:

  1. 谁创造了对象 [工厂]:你必须写HOW对象应该被创建。您有单独的Factory类,其中包含创建逻辑。 [依赖注入]:在实际情况下由外部框架完成(例如在Java中将是spring / ejb / guice)。注入“神奇地”发生而没有明确地创建新对象
  2. 它管理什么样的对象: [工厂]:通常负责创建有状态对象 [依赖注入]更有可能创建无状态对象

实例如何在单个项目中使用工厂和依赖注入

  1. 我们想要建立什么

用于创建订单的应用程序模块,其中包含多个称为订单行

  1. 建筑

我们假设我们要创建以下层架构:

enter image description here

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

  1. 域层和工厂的使用

将在数据库中的实体是Order和OrderLine。订单可以有多个OrderLines。 Relationship between Order and OrderLine

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

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

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

enter image description here

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

  1. 依赖注入依赖注入更常用于无状态层,例如存储库和服务。

OrderRepository和OrderService由依赖注入框架管理。存储库负责管理数据库上的CRUD操作。 Service注入Repository并使用它来保存/查找正确的域类。

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.