Dependency Injection vs Factory Pattern
引用的大多数例子都是关于依赖注入的,我们也可以使用工厂模式来解决。在使用/设计方面,依赖项注入和工厂之间的区别似乎很模糊或很薄。
有一次有人告诉我你是怎么使用它的,这会有很大的不同!
我曾经使用structuremap一个DI容器来解决一个问题,后来我重新设计了它,使其与一个简单的工厂一起工作,并删除了对structuremap的引用。
有人能告诉我他们之间的区别和在哪里使用什么,这里的最佳实践是什么?
当使用工厂时,您的代码实际上仍然负责创建对象。通过DI,您可以将该职责外包给另一个类或框架,该类或框架独立于您的代码。
我建议保持概念简单明了。依赖注入更像是松散耦合软件组件的体系结构模式。工厂模式只是将创建其他类对象的责任分离到另一个实体的一种方法。工厂模式可以作为实现DI的工具调用。依赖注入可以通过许多方式实现,如使用构造函数的DI、使用映射XML文件等。
依赖注入
汽车不需要对零件本身进行实例化,而是要求零件能够正常工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; } } |
工厂
将各个部分组合在一起以形成一个完整的对象,并从调用方隐藏具体的类型。
1 2 3 4 5 6 7 8 9 10 11 | 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是相辅相成的。
1 2 3 4 5 | static void Main() { ICar car = CarFactory.BuildCar(); // use car } |
你还记得金发姑娘和三只熊吗?依赖注入就是这样。这里有三种方法来做同样的事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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-这是理想的,因为该方法要求准确地满足其需要。不要太多或太少。我不需要写一个mockcarfactory来创建mockcars,我可以直接通过mockcar。它是直接的,接口不会说谎。
Misko Hevery的这篇Google技术演讲非常精彩,也是我从中得出例子的基础。http://www.youtube.com/watch?V= XCT4YYYTTTS
有一些问题很容易用依赖注入解决,而用一组工厂解决不了。
一方面,控制反转和依赖注入(IOC/DI)与服务定位器或一套工厂(工厂)之间的一些区别是:
IOC/DI本身就是一个完整的域对象和服务生态系统。它按照您指定的方式为您设置所有内容。您的域对象和服务是由容器构造的,而不是自己构造的:因此它们不依赖于容器或任何工厂。IOC/DI允许非常高的可配置性,所有配置都在应用程序最顶层(GUI、Web前端)的单个位置(容器的构造)中。
工厂抽象了一些域对象和服务的构造。但是域对象和服务仍然负责弄清楚如何构建它们自己以及如何获得它们所依赖的所有东西。所有这些"活动"依赖项都会一直过滤应用程序中的所有层。没有一个地方可以配置所有内容。
依赖注入(DI)和工厂模式相似的原因是它们是控制反转(IOC)的两种实现,这是一种软件体系结构。简单地说,它们是同一问题的两种解决方案。
因此,要回答这个问题,工厂模式和DI之间的主要区别在于如何获得对象引用。将依赖项注入作为名称意味着将引用注入或提供给代码。对于工厂模式,代码必须请求引用,以便代码获取对象。这两种实现都会移除或分离代码与代码所使用的对象引用的基础类或类型之间的链接。
值得注意的是,工厂模式(或者实际上是抽象的工厂模式,即返回返回对象引用的新工厂的工厂)可以被写入以动态选择或链接到运行时请求的对象的类型或类。这使得它们与服务定位器模式非常相似(甚至比DI更相似),后者是IOC的另一个实现。
工厂设计模式相当古老(就软件而言),已经存在一段时间了。由于近来国际奥委会建筑格局的流行,它正在复苏。
我想当涉及到IOC设计模式时:注入器被注入,定位器被定位,工厂被重构。
DI的一个缺点是它不能用逻辑初始化对象。例如,当我需要创建具有随机名称和年龄的字符时,DI不是工厂模式的选择。使用工厂,我们可以很容易地从对象创建中封装随机算法,它支持一种称为"封装变化内容"的设计模式。
Life cycle management is one of the responsibilities dependency containers assume in addition to instantiation and injection. The fact that the container sometimes keep a reference to the components after instantiation is the reason it is called a"container", and not a factory. Dependency injection containers usually only keep a reference to objects it needs to manage life cycles for, or that are reused for future injections, like singletons or flyweights. When configured to create new instances of some components for each call to the container, the container usually just forgets about the created object.
来自:http://tutorials.jenkov.com/dependency-injection/dependency-injection-containers.html
我相信DI是一种工厂抽象层,但它们也提供了抽象之外的好处。真正的工厂知道如何实例化一个类型并对其进行配置。好的DI层通过配置提供了实例化和配置多种类型的能力。
显然,对于一个在构建过程中需要相对稳定的业务逻辑的简单类型的项目,工厂模式很容易理解、实现和工作良好。
Otoh,如果您有一个包含许多类型的项目,而您希望经常更改这些类型的实现,那么DI通过其配置为您提供了在运行时执行此操作的灵活性,而不必重新编译您的工厂。
我知道这个问题很老,但我想加上我的5美分,好的。
我认为依赖注入(DI)在很多方面类似于可配置工厂模式(FP),从这个意义上说,您可以用DI做任何事情,您都可以用这样的工厂来做。好的。
实际上,如果您使用Spring,那么您可以选择自动布线资源(DI)或执行类似的操作:好的。
1 | MyBean mb = ctx.getBean("myBean"); |
然后使用那个"mb"实例做任何事情。这不是一个对工厂的调用,它会返回一个实例吗??好的。
我注意到的大多数fp示例之间唯一的区别是,您可以配置XML或其他类中的"mybean",框架将作为工厂工作,但除此之外,这是相同的,并且您可以拥有一个工厂,它可以读取配置文件或根据需要获取实现。好的。
如果你问我的意见(我知道你没有),我相信DI做了同样的事情,但只是增加了开发的复杂性,为什么?好的。
好吧,首先,为了让您知道您与DI自动连接的任何bean所使用的实现是什么,您必须转到配置本身。好的。
但是…那你不必知道你所使用对象的实现的承诺呢?PFFT!真的吗?当你使用这样的方法时…您和编写实现的人不一样吗??即使你不这样做,你不是一直都在关注实现应该如何做吗??好的。
最后一点,无论DI框架向您承诺了多少,您将构建与之分离的东西,而不依赖于它们的类,如果您使用的是一个框架,您将围绕它构建所有东西,如果您必须更改方法或框架,这将不是一个简单的任务…永远!…但是,由于您构建的一切都围绕着这个特定的框架,而不是担心什么是您的业务的最佳解决方案,那么在这样做时,您将面临一个biiig问题。好的。
事实上,对于FP或DI方法,我能看到的唯一真正的业务应用程序是,如果您需要更改运行时使用的实现,但是至少我知道的框架不允许您这样做,那么您必须在开发时将配置中的所有内容都保持完美,如果您需要,请使用另一种方法。好的。
因此,如果我有一个类在同一个应用程序的两个作用域中执行不同(例如,一个控股公司的两个公司),我必须配置框架以创建两个不同的bean,并调整代码以使用每个bean。这和我写这样的东西不一样吗?好的。
1 2 | MyBean mb = MyBeanForEntreprise1(); //In the classes of the first enterprise MyBean mb = MyBeanForEntreprise2(); //In the classes of the second enterprise |
与此相同:好的。
1 2 | @Autowired MyBean mbForEnterprise1; //In the classes of the first enterprise @Autowired MyBean mbForEnterprise2; //In the classes of the second enterprise |
而这:好的。
1 2 | 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 |
在任何情况下,您都必须更改应用程序中的某些内容,无论是类还是配置文件,但是您必须重新部署它。好的。
就这样做不是很好吗:好的。
1 | MyBean mb = (MyBean)MyFactory.get("mb"); |
这样,您就设置了工厂的代码,以便在运行时根据登录的用户企业获得正确的实现??那会有帮助的。您可以用新的类添加一个新的JAR,甚至在运行时设置规则(或者如果您保持此选项处于打开状态,则添加一个新的配置文件),而不更改现有的类。这将是一个充满活力的工厂!好的。
这难道不比为每个企业编写两个配置,甚至为每个企业编写两个不同的应用程序更有用吗??好的。
你可以告诉我,我不需要在运行时进行切换,所以我配置了应用程序,如果我继承了类或使用其他实现,我只需更改配置并重新部署。好的,那也可以在工厂里完成。老实说,你做了多少次?也许只有当你有一个应用程序将在你公司的其他地方使用,你将把代码传递给另一个团队,他们才会这样做。但是,嘿,这也可以用工厂来完成,用一个充满活力的工厂会更好!!好的。
不管怎样,评论区如果开放让你杀了我。好的。好啊。
理论
有两个重要的考虑点:
谁创建对象
- [工厂]:您必须编写如何创建对象。您有单独的包含创建逻辑的工厂类。
- [依赖注入]:在实际情况下是由外部框架(例如在Java中,将是Spring/EJB/Guice)来完成的。注入"神奇地"发生,没有明确的新对象创建
它管理的对象类型:
- [工厂]:通常负责创建有状态对象
- [依赖项注入]更可能创建无状态对象
如何在单个项目中同时使用工厂和依赖注入的实例
用于创建包含多个名为orderline的条目的订单的应用程序模块。
假设我们要创建以下层体系结构:
域对象可以是存储在数据库中的对象。存储库(DAO)帮助从数据库中检索对象的retrievar。服务向其他模块提供API。用于
数据库中的实体是order和orderline。订单可以有多个订单行。
现在是重要的设计部分。除此之外的模块是否应自行创建和管理订单行?不可以。只有当订单与订单关联时,订单行才应该存在。最好将内部实现隐藏到外部类中。
但如何在不了解订单行的情况下创建订单?
工厂
希望创建新订单的用户OrderFactory(这将隐藏有关如何创建订单的详细信息)。
这就是它在IDE中的外观。
OrderRepository和OrderService由依赖项注入框架管理。存储库负责管理数据库上的CRUD操作。服务注入存储库并使用它来保存/查找正确的域类。
国际奥委会是一个通过两种方式实现的概念。依赖创建和依赖注入,工厂/抽象工厂是依赖创建的例子。依赖注入是构造函数、setter和接口。IOC的核心不是依赖于具体的类,而是定义方法的抽象(比如接口/抽象类),并使用该抽象来调用具体类的方法。像工厂模式一样返回基类或接口。相似依赖注入使用基类/接口为对象设置值。
通过依赖注入,客户机不需要依赖于自己,而是预先准备好的。
对于工厂,必须有人调用这些对象才能将生成的对象转移到需要它们的地方。
区别主要在于这一行,调用工厂并获取构造的对象。
但是对于工厂,你必须在任何需要这样一个对象的地方写这一行。使用DI,您只需创建一次连接(使用和创建的对象之间的关系),然后在任何地方都依赖对象的存在。另一方面,DI在准备方面通常需要更多的工作(多少取决于框架)。
我一读到关于DI的文章,就有了同样的问题,并在这篇文章中结束了。所以最后这是我理解的,但是如果我错了请纠正我。
"很久以前,很少有王国拥有自己的管理机构,根据自己的书面规则来控制和作出决定。后来成立了一个大政府,废除了所有这些小的管理机构,这些机构有一套规则(宪法),并通过法院来执行。
小王国的管理机构是"工厂"
大政府是"依赖注入器"。
您可以查看此链接,以在实际示例中比较两种(和其他)方法。
基本上,当需求发生变化时,如果使用工厂而不是DI,最终会修改更多的代码。
这对于手动DI也是有效的(即,当没有外部框架为对象提供依赖项,但在每个构造函数中传递它们时)。
我相信DI是一种配置或实例化bean的方法。DI可以通过许多方式完成,如构造器、setter getter等。
工厂模式只是实例化bean的另一种方法。此模式主要在必须使用工厂设计模式创建对象时使用,因为使用此模式时,您不配置bean的属性,而只实例化对象。
检查此链接:依赖项注入
我认为,有三个重要方面控制对象及其用法:1。实例化(类的实例化,如果有初始化)。2。在需要的地方注入(如此创建的实例)。三。生命周期管理(针对如此创建的实例)。使用工厂模式,可以实现第一个方面(实例化),但剩下的两个方面存在疑问。使用其他实例的类必须对工厂进行硬编码(而不是正在创建的实例),这会妨碍松散耦合能力。此外,实例的生命周期管理在一个工厂在多个地方使用的大型应用程序中成为一个挑战(特别是,如果工厂不管理返回的实例的生命周期,它会变得丑陋)。另一方面,使用一个DI(IOC模式),所有3个都是在代码之外抽象出来的(到DI容器),并且托管bean不需要考虑这种复杂性。松耦合,一个非常重要的建筑目标可以实现安静舒适。另一个重要的体系结构目标是,可以比工厂更好地实现关注点的分离。
虽然工厂可能适合小型应用,但大型工厂最好选择DI而不是工厂。
Binoj
我认为你不必选择其中一个。
将依赖类或接口移动到类构造函数或setter的操作遵循DI模式。传递给构造函数或集合的对象可以使用工厂实现。
什么时候使用?使用开发人员驾驶室中的一个或多个模式。他们觉得最舒服和最容易理解的是什么?
对于工厂,您可以对相关接口进行分组,因此,如果传递的参数可以在工厂中分组,那么对于
1 2 3 4 5 6 7 8 9 10 11 12 | 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; } |
看看构造函数,您只需要在那里传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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,... |
你在
*)代码来自nopcommerce。
注入框架是工厂模式的实现。
这完全取决于你的要求。如果您需要在应用程序中实现工厂模式,那么您的需求很可能会被大量的注入框架实现之一所满足。
只有当任何第三方框架都不能满足您的需求时,您才应该推出自己的解决方案。你写的代码越多,你需要维护的代码就越多。代码是负债而不是资产。
关于应该使用哪种实现的争论并没有理解应用程序的体系结构需求那么重要。
这里的大多数答案解释了这两者的概念差异和实现细节。然而,我找不到关于应用差异的解释,IMO是最重要的,OP询问了这一点。所以让我重新打开这个话题…
Once someone told me that its how you use it that makes a difference!
确切地。在90%的情况下,您可以使用factory或di获取对象引用,通常最后会得到后者。在另外10%的情况下,使用工厂是唯一正确的方法。这些情况包括在运行时参数中通过变量获取对象。这样地:
1 2 | IWebClient client = factoryWithCache.GetWebClient(url:"stackoverflow.com", useCookies: false, connectionTimeout: 120); |
在这种情况下,从DI获取
从面值来看,它们看起来是一样的
简单地说,工厂模式,一个创造性的模式帮助我们创建一个对象-"定义一个用于创建对象的接口"。如果我们有一个键值类型的对象池(例如字典),将键传递给工厂(我指的是简单的工厂模式),您可以解析类型。工作完成了!另一方面,依赖注入框架(如结构图、Ninject、Unity等)似乎也在做同样的事情。
但是……"不要重新发明轮子"
从建筑的角度来看,它是一个结合层,并且"不要再重新发明轮子"。
对于企业级应用程序,DI的概念更像是定义依赖关系的架构层。为了进一步简化这一过程,您可以将其视为一个单独的类库项目,它可以解决依赖关系。主应用程序依赖于此项目,其中依赖关系解析器引用其他具体实现和依赖关系解析。
除了工厂的"gettype/create"之外,我们通常还需要更多的特性(使用XML定义依赖项、模拟和单元测试等的能力)。由于您提到了结构图,请查看结构图功能列表。很明显,这不仅仅是简单地解析简单的对象映射。不要重新发明轮子!
如果你只有一把锤子,一切看起来都像钉子
根据您的需求以及您构建的应用程序类型,您需要做出选择。如果它只有几个项目(可能是一个或两个….),并且依赖性很少,那么您可以选择一种更简单的方法。这就像使用ADO.NET数据访问而不是使用实体框架进行简单的1或2个数据库调用,在这种情况下,引入EF是一种过度杀伤力。
但是对于一个更大的项目,或者如果你的项目变得更大,我强烈建议使用一个带有框架的DI层,并腾出空间来更改你使用的DI框架(在主应用程序(Web应用程序、Web API、桌面等)中使用外观)。
工厂设计模式
工厂设计模式的特点是
- 接口
- 实现类
- 工厂
当你像下面这样质疑自己时,你可以观察到一些事情
- 工厂何时为实现类创建对象-运行时还是编译时?
- 如果您想在运行时切换实现呢?-不可能
这些由依赖注入处理。
依赖注入
您可以使用不同的方法来注入依赖项。为了简单起见,让我们来看看接口注入
在DI中,容器创建所需的实例,并将它们"注入"到对象中。
这样就消除了静态实例化。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class MyClass{ MyInterface find= null; //Constructor- During the object instantiation public MyClass(MyInterface myInterface ) { find = myInterface ; } public void myMethod(){ find.doSomething(); } } |
我的想法是:
依赖注入:将合作者作为参数传递给构造函数。依赖注入框架:一个通用的、可配置的工厂,用于创建要作为参数传递给构造函数的对象。
简单来说,依赖注入和工厂方法分别意味着推拉机制。
拉机制:类间接依赖工厂方法,而工厂方法又依赖具体类。
推送机制:根组件可以在单个位置配置所有依赖组件,从而促进高维护和松耦合。
对于工厂方法,责任仍然由类(尽管是间接的)来创建新的对象,与依赖项注入一样,责任是外包的(尽管以泄漏抽象为代价)。
我认为这些是正交的,可以一起使用。我来举一个我最近在工作中遇到的例子:
我们使用Java中的Spring框架进行DI。单个类(
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @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); // ... } } |
在本例中,
这是我意识到一个
这是简化的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | @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 | ICarBusiness carBusiness = BusinessFactory.CreateCarBusiness(); |
另一个开发人员将看到这一点,当他在BusinessFactory中创建业务层对象时,IntelliSense会为开发人员提供所有可能要创建的业务层。不必玩游戏,找到我想要创建的界面。
这种结构已经是控制反转。我不再负责创建特定对象。但是您仍然需要确保依赖注入能够轻松地改变事情。创建自己的自定义依赖注入是荒谬的,所以我使用Unity。在createCabusiness()中,我要求Unity解析哪个类属于这个类以及它的生存期。
所以我的代码工厂依赖注入结构是:
1 2 3 4 5 6 7 | public static class BusinessFactory { public static ICarBusiness CreateCarBusiness() { return Container.Resolve<ICarBusiness>(); } } |
现在我有了两者的好处。对于其他开发人员来说,我的代码对于我所使用的对象的作用域也更具可读性,而不是构造函数依赖项注入,后者只表示在创建类时每个对象都可用。
在创建单元测试时,我使用它将数据库数据访问更改为自定义的编码数据访问层。我不希望我的单元测试与数据库、Web服务器、电子邮件服务器等通信。它们需要测试我的业务层,因为这就是智能所在的地方。
如果您是:1。在小分区中部署代码,因为它可以很好地处理一个大代码的去耦。2。可测试性是DI可以使用的情况之一,因为您可以很容易地模拟未分离的对象。通过使用接口,您可以轻松地模拟和测试每个对象。三。您可以同时修改程序的每个部分,而无需编写另一部分的代码,因为它是松散分离的。