依赖注入与工厂模式的区别

575
大多数关于依赖注入使用的例子,我们也可以使用工厂模式来解决。看起来在使用/设计时,依赖注入和工厂之间的区别模糊或微小。
有人曾经告诉我,它的不同在于你如何使用它!
我曾经使用StructureMap作为DI容器来解决问题,后来重新设计为使用简单的工厂,并删除了对StructureMap的引用。
有人能告诉我它们之间的区别以及在何处使用什么,这里最好的实践是什么?

27
这两种方法不能互相补充吗:使用依赖注入来注入工厂类? - Richard Ev
21
如果这个问题有一些代码作为答案会很好!我还是不明白依赖注入与使用工厂来创建有什么好处/区别?只需要在工厂类中更改一行代码即可更改创建的对象/实现,这不就可以了吗? - gideon
2
@gideon 那不会强制你编译你的应用程序,或者至少是包含工厂类的模块吗? - lysergic-acid
1
@liortal 是的,没错。自从那条评论以来,我进行了长时间的依赖注入研究,现在我明白 DI 将工厂方法推进了一步。 - gideon
1
看看这个很棒的答案:https://dev59.com/5W445IYBdhLWcg3wNXc_ - 他表达得非常清晰,并提供了代码示例。 - Luis Perez
显示剩余4条评论
31个回答

6

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


6
使用依赖注入框架,开发者无需手动准备和设置类实例的依赖项,所有的都已预先准备好了。
使用工厂时,开发者需要手动完成这些工作,并使用这些依赖对象创建类实例。
区别主要在于调用工厂并获取构造对象的那一行代码,以及编写创建和设置所有内容的工厂方法(尽管可以认为使用依赖注入框架也必须在一定程度上完成这一点,通过连接和配置对象关系)。
然后,使用工厂需要在需要此类对象的任何位置调用工厂。使用 DI 框架,您通常可以在类实例创建时依赖于对象的存在。
我的看法是,工厂方法更加静态,因为其实现相当固定,而依赖注入框架更加动态,因为类实例的实际组成可以更轻松地在运行时进行更改(例如,用于测试目的)。

2
你所描述的不是依赖注入(DI)。你谈论的是DI框架,它可以在编译时为您重写代码。该模式本身与您所描述的内容没有任何关联。 - yehonatan yehezkel
1
@yehonatanyehezkel 更新了文本以更好地表达我的答案与框架相关,而不是设计模式。 - Kosi2801

4

我的想法:

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


2
没错,几乎所有提到“依赖注入”(DI)的问题和回答都与这个定义不符。依赖注入容器(DIC)是最常见的框架之一,作为通用和可配置的工厂来创建对象。 - CXJ

3

通过使用工厂,您可以将相关接口分组。因此,如果传递的参数可以在工厂中进行分组,那么它也是解决构造函数过度注入的好方法,请参考以下代码*):

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 中看到了许多传递的参数,是的,您可以将其视为 构造函数过度注入 但这就是 DI 的工作原理。并且 CustomerController 没有任何问题。

*) 代码来自 nopCommerce。


3
大多数答案都解释了两者的概念差异和实现细节。然而,我无法找到关于应用程序差异的解释,这是我认为最重要的,并且OP也在询问。因此,让我们重新开启这个话题...

有人曾告诉我,使用方式才是真正的区别所在!

没错。在90%的情况下,您可以使用工厂或DI来获取对象引用,通常您最终会选择后者。在另外10%的情况下,使用工厂是唯一正确的方法。这些情况包括通过变量在运行时参数中获取对象。像这样:

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

在这种情况下,从 DI 中获取 client 不可能(或至少需要一些丑陋的解决方法)。因此,作为一个通用规则来做决策:如果可以在没有任何运行时计算参数的情况下获取依赖项,则优先使用 DI,否则使用工厂。

3
当我读到DI时,我也有同样的问题,并最终看到了这篇文章。所以,这是我理解的内容,请纠正我如果我理解错了。
“很久以前,有许多小王国,它们有自己的管理机构,根据自己制定的规则做出决策。后来形成了一个大政府,消除了所有这些小管理机构,只有一套规则(宪法),并通过法院实施。”
小王国的管理机构就是“工厂”。
大政府就是“依赖注入器”。

1
那么,什么时候才能认为之前的小型政府现在已经变得庞大了?按照什么标准来衡量呢? - Prisoner ZERO

3
您可以查看此链接,以了解在实际示例中两种(和其他)方法的比较。
基本上,如果使用工厂而不是 DI,则在需求变更时需要修改更多的代码。
这也适用于手动 DI(即当没有外部框架为您的对象提供依赖项时,但您会在每个构造函数中传递它们)。

3
简单来说,依赖注入和工厂方法意味着推送和拉取机制。拉取机制:类间接地依赖于工厂方法,后者又依赖于具体类。推送机制:根组件可以在一个位置配置所有依赖组件,从而促进高维护性和松散耦合。使用工厂方法时,责任仍然在类上(虽然是间接的)创建新对象,而使用依赖注入,则将该责任外包(代价是泄漏抽象)。

@RahulAgarwal,最后一部分“_at the cost of leaking abstraction though_”是什么意思? DI以一种工厂不会的方式泄漏抽象吗? - jaco0646

2

当您确切知道需要哪些对象时,可以使用依赖注入。而在工厂模式中,由于您不确定需要哪种类型的对象,因此只需将对象创建过程委托给工厂即可。


2
我认为,有三个重要方面影响对象及其使用:
1. 实例化(类的实例化以及初始化)。
2. 注入(所创建的实例在需要时进行注入)。
3. 生命周期管理(所创建的实例的生命周期管理)。

使用工厂模式可以实现第一个方面(实例化),但是剩下的两个方面存在问题。使用其他实例的类必须硬编码工厂(而不是创建实例),这会阻碍松耦合的能力。此外,在大型应用程序中使用工厂在多个地方使用时,实例的生命周期管理将成为一项挑战(特别是如果工厂不管理它返回的实例的生命周期,情况将变得很糟糕)。

另一方面,使用DI(IoC模式)将所有三个方面抽象到代码之外(到DI容器),托管的bean与此复杂性无关。可以轻松实现非常重要的架构目标松散耦合。比工厂更好地实现了另一个重要的架构目标关注点分离

虽然工厂可能适用于小型应用程序,但大型应用程序最好选择DI而不是工厂。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接