引用依赖注入的大多数示例,我们也可以使用工厂模式来解决。看起来在使用/设计时,依赖项注入和工厂之间的差异变得模糊或稀薄。
曾经有人告诉我,您如何使用它与众不同!
我曾经使用StructureMap一个DI容器来解决问题,后来我对其进行了重新设计以使其与简单的工厂一起工作,并删除了对StructureMap的引用。
谁能告诉我它们之间的区别以及在哪里使用什么,这里的最佳做法是什么?
引用依赖注入的大多数示例,我们也可以使用工厂模式来解决。看起来在使用/设计时,依赖项注入和工厂之间的差异变得模糊或稀薄。
曾经有人告诉我,您如何使用它与众不同!
我曾经使用StructureMap一个DI容器来解决问题,后来我对其进行了重新设计以使其与简单的工厂一起工作,并删除了对StructureMap的引用。
谁能告诉我它们之间的区别以及在哪里使用什么,这里的最佳做法是什么?
Answers:
使用工厂时,您的代码实际上仍然负责创建对象。通过DI,您可以将该责任外包给与您的代码分离的另一个类或框架。
Manually-Injected Dependency
我建议保持概念简单明了。依赖注入更多是一种松散耦合软件组件的体系结构模式。工厂模式只是将创建其他类的对象的职责分离到另一个实体的一种方法。可以将工厂模式称为实现DI的工具。依赖注入可以通过多种方式实现,例如使用构造函数进行DI,使用映射xml文件等。
依赖注入
汽车没有实例化零件本身,而是要求其需要起作用的零件。
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();
}
Example#1-这是最糟糕的,因为它完全隐藏了依赖关系。如果您将该方法看成一个黑匣子,您将不会知道它需要一辆汽车。
例子2-更好一点,因为现在我们知道我们需要汽车,因为我们经过汽车制造厂。但是这次我们通过的太多了,因为实际上所有方法都需要一辆汽车。当汽车可以在方法之外制造并通过时,我们正在通过一家工厂来制造汽车。
Example#3-这是理想的,因为该方法确切地要求它需要什么。不会太多或太少。我不必编写MockCarFactory即可创建MockCars,我可以直接传递模拟。它是直接的,接口也不会说谎。
Misko Hevery撰写的Google技术讲座令人惊叹,它是我从中得出例子的基础。http://www.youtube.com/watch?v=XcT4yYu_TTs
依赖注入(DI)和工厂模式相似的原因是因为它们是软件结构控制反转(IoC)的两种实现。简单地说,它们是针对同一问题的两种解决方案。
因此,要回答这个问题,Factory模式和DI之间的主要区别是如何获取对象引用。依赖注入的名称暗示该引用已注入或提供给您的代码。使用Factory模式,您的代码必须请求引用,以便您的代码访存该对象。两种实现都删除或分离了代码与代码所使用的对象引用的基础类或类型之间的链接。
值得注意的是,可以编写Factory模式(或者实际上是Abstract Factory模式,即返回新工厂并返回对象引用的工厂)是在运行时动态选择或链接到所请求对象的类型或类。这使它们与IoC的另一种实现的Service Locator模式非常相似(甚至比DI更是如此)。
工厂设计模式(在软件方面)非常古老,已经存在了一段时间。自从最近架构模式IoC流行以来,它正在复苏。
我想说到IoC设计模式:注入器正在注入,定位器正在定位,工厂已经重构。
有一些问题可以通过依赖注入轻松解决,而使用工厂套件很难解决。
一方面,控制反转和依赖项注入(IOC / DI)与另一方面,服务定位器或工厂套件(工厂)之间的某些区别是:
IOC / DI是域对象和服务本身的完整生态系统。它以您指定的方式为您设置一切。您的域对象和服务是由容器构造的,而不是自己构造的:因此,它们与容器或任何工厂没有任何依赖关系。IOC / DI允许极高的可配置性,所有配置都位于应用程序最顶层(GUI,Web前端)的单个位置(容器的构造)。
Factory提取了一些域对象和服务的构造。但是领域对象和服务仍然负责弄清楚如何构造自己以及如何获得它们所依赖的所有东西。所有这些“活动”依赖项都会一直过滤应用程序中的所有层。没有一个地方可以配置所有东西。
生命周期管理是依赖容器除了实例化和注入之外还要承担的职责之一。容器在实例化后有时会保留对组件的引用这一事实是将其称为“容器”而不是工厂的原因。依赖注入容器通常仅保留其管理生命周期所需的对象的引用,或将其重复用于将来的注入,例如单例或重量级。当配置为每次调用容器创建某些组件的新实例时,容器通常只会忘记创建的对象。
来自:http : //tutorials.jenkov.com/dependency-injection/dependency-injection-containers.html
我相信DI是工厂的一种抽象层,但它们也提供了抽象之外的好处。真正的工厂知道如何实例化单个类型并对其进行配置。好的DI层通过配置可以实例化和配置许多类型。
显然,对于具有几个简单类型的项目,这些项目在其构建中需要相对稳定的业务逻辑,因此工厂模式易于理解,实施且运作良好。
OTOH,如果您的项目包含许多您希望经常更改其实现的类型,则DI通过其配置为您提供了灵活性,使其可以在运行时执行此操作而不必重新编译工厂。
有两点要考虑:
谁创造对象
它管理哪种对象:
用于创建订单的应用程序模块,其中包含多个称为订单行的条目。
假设我们要创建以下层体系结构:
域对象可以是存储在数据库内部的对象。存储库(DAO)帮助从数据库中检索对象。服务向其他模块提供API。允许在order
模块上进行操作
将在数据库中的实体是Order和OrderLine。订单可以有多个OrderLines。
现在是重要的设计部分。这个模块之外的模块是否应该自己创建和管理OrderLine?否。仅当您有与订单相关联时,订单行才应存在。最好将内部工具隐藏在外部类中。
但是,如何在不了解OrderLines的情况下创建Order?
厂
想要创建新订单的人使用了OrderFactory(这将隐藏有关我们如何创建Order的事实的详细信息)。
那就是它在IDE中的外观。domain
包外部的类将OrderFactory
代替内部的构造函数Order
OrderRepository和OrderService由依赖项注入框架管理。存储库负责管理数据库上的CRUD操作。服务注入存储库,并使用它来保存/查找正确的域类。
[Factory]: Usually responsible for creation of stateful objects [Dependency Injections] More likely to create stateless objects
我不明白为什么
我知道这个问题很旧,但是我想加五分钱,
我认为依赖注入(DI)在许多方面都类似于可配置的工厂模式(FP),从这种意义上讲,您可以使用DI进行的任何操作都可以在这种工厂中完成。
实际上,例如,如果您使用spring,则可以选择自动装配资源(DI)或执行以下操作:
MyBean mb = ctx.getBean("myBean");
然后使用该“ mb”实例执行任何操作。这不是对工厂的电话,它将返回您一个实例吗?
在大多数FP示例之间,我注意到的唯一真正的区别是,您可以配置xml或另一个类中的“ myBean”,并且框架将作为工厂工作,但除此之外,这是相同的,并且您可以肯定有一个Factory来读取配置文件或根据需要获取实现。
而且,如果您问我意见(我知道您没有),我相信DI可以做同样的事情,但是只会增加开发的复杂性,为什么呢?
好吧,一方面,要让您知道与DI自动装配的任何bean所使用的实现是什么,您必须去配置本身。
但是...那将使您不必知道所使用对象的实现的承诺又如何呢?pfft!认真吗?当您使用这样的方法时...编写实现的方法不一样吗?即使您不这样做,也几乎一直都在观察实现如何执行应做的工作?
最后一点是,DI框架向您承诺多少,您将构建与之分离的事物并不依赖它们的类,这无关紧要;如果您使用的是框架,则必须构建它周围的所有内容,如果您必须改变方法或框架绝非易事...永远!...但是,由于您围绕该特定框架构建了所有内容,而不用担心什么是最适合您业务的解决方案,因此您在这样做时会遇到麻烦。
实际上,我可以看到的唯一用于FP或DI方法的实际业务应用程序是是否需要更改运行时使用的实现,但是至少我所知道的框架不允许您这样做,所以您必须离开开发时配置中的所有内容都很完美,如果需要,可以使用另一种方法。
因此,如果我有一个类在同一个应用程序的两个范围内执行不同的类(比方说,一家控股公司的两家公司),则必须配置该框架以创建两个不同的bean,并修改我的代码以使用每个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并甚至在运行时设置规则(或者,如果您将此选项保持打开状态,则添加新的配置文件),而无需更改现有类。这将是一个动态工厂!
不必为每个企业编写两个配置,甚至为每个企业编写两个不同的应用程序?
您可以告诉我,我不需要在运行时进行切换,因此可以配置应用程序,并且如果我继承该类或使用其他实现,则只需更改配置并重新部署即可。好的,这也可以在工厂完成。老实说,您执行了几次?也许只有当您的应用程序将在公司的其他地方使用,并且您要将代码传递给另一个团队时,他们才会做这样的事情。但是,这也可以通过工厂来完成,而对于动态工厂则更好!!
无论如何,如果有评论栏目供您杀死我。
您可以查看此链接,以比较实际示例中的两种(和其他)方法。
基本上,当需求改变时,如果使用工厂而不是DI,则最终会修改更多的代码。
这对于手动DI也有效(即,当没有外部框架提供对对象的依赖关系,但您在每个构造函数中传递它们时)。
这里的大多数答案都说明了两者的概念差异和实现细节。但是,我找不到关于IMO最重要且OP询问的应用程序差异的解释。所以让我重新打开这个话题...
曾经有人告诉我,您如何使用它与众不同!
究竟。在90%的情况下,您可以使用Factory或DI获取对象引用,并且通常以后者为最终对象。在另外10%的情况下,使用Factory 只是正确的方法。这些情况包括在运行时参数中通过变量获取对象。像这样:
IWebClient client = factoryWithCache.GetWebClient(url: "stackoverflow.com",
useCookies: false, connectionTimeout: 120);
在这种情况下,client
无法从DI 获取(或至少需要一些难看的解决方法)。因此作为决策的一般规则:如果可以在没有运行时计算的参数的情况下获得依赖关系,则首选DI,否则使用Factory。
比诺伊
我认为您不必选择一个。
将依赖类或接口移至类构造函数或设置器的操作遵循DI模式。传递给构造函数或集合的对象可以使用Factory来实现。
什么时候使用?使用开发人员平台中的一个或多个模式。他们觉得最舒适,最容易理解的是什么。
我相信,三个重要方面决定着对象及其用法:
1. 实例化(类的初始化和初始化,如果有的话)。
2. 在需要的地方注入(这样创建的实例)。
3. (如此创建的实例的)生命周期管理。
使用Factory模式,可以实现第一个方面(实例化),但是其余两个方面是有问题的。使用其他实例的类必须对工厂进行硬编码(而不是创建实例),这会阻碍松散的耦合能力。此外,生命周期管理在大型应用程序中,在多个地方使用工厂是一个挑战(特别是,如果工厂不管理实例返回的实例的生命周期,它将变得很丑)。
另一方面,使用DI(具有IoC模式),所有3个代码都将抽象到代码外部(到DI容器中),并且托管Bean不需要这种复杂性。松散耦合可以舒适地安静地实现非常重要的建筑目标。另一个重要的体系结构目标是,关注点的分离比工厂要好得多。
尽管工厂可能适用于小型应用,但大型工厂最好选择DI,而不是工厂。
工厂设计模式
工厂设计模式的特点是
当您质疑自己时,您可以观察到以下几点:
这些通过依赖注入来处理。
依赖注入
您可以有不同的方式来注入依赖项。为简单起见,让我们进行接口注入
在DI中,容器创建所需的实例,然后将其“注入”到对象中。
从而消除了静态实例化。
例:
public class MyClass{
MyInterface find= null;
//Constructor- During the object instantiation
public MyClass(MyInterface myInterface ) {
find = myInterface ;
}
public void myMethod(){
find.doSomething();
}
}
从面值看,它们看起来一样
用非常简单的术语来说,“工厂模式”即“创建模式”有助于创建一个对象-“定义用于创建对象的接口”。如果我们有一个键值类型的对象池(例如字典),将键传递给工厂(我指的是简单工厂模式),则可以解析类型。任务完成!另一方面,依赖注入框架(例如Structure Map,Ninject,Unity ...等)似乎在做同样的事情。
但是...“不要重新发明轮子”
从架构的角度看,它是一个绑定层,并且“不要重新发明轮子”。
对于企业级应用程序,DI的概念更多是定义依赖关系的体系结构层。为了进一步简化,您可以将其视为一个单独的类库项目,该项目可以解决依赖项。主要应用程序依赖于该项目,其中依赖关系解析程序引用了其他具体实现以及依赖关系解析。
除了工厂提供的“ GetType / Create”之外,通常我们还需要更多功能(使用XML定义依赖项,模拟和单元测试等功能)。由于您已经参考了“结构图”,因此请查看“ 结构图”功能列表。显然,这不仅仅是解决简单的对象映射。不要重新发明轮子!
如果您只有锤子,那么一切看起来就像钉子
根据您的要求和您构建的应用程序类型,您需要做出选择。如果它只有几个项目(可能是一个或两个..),并且涉及的依赖很少,则可以选择一种更简单的方法。就像通过简单的1或2个数据库调用使用Entity Framework来使用ADO .Net数据访问一样,在这种情况下,引入EF可能会显得过分。
但是对于较大的项目或项目变得更大,我强烈建议在DI层中添加一个框架,并留出空间来更改所使用的DI框架(在主应用程序(Web App,Web Api,Desktop)中使用Facade ..等等。)。
在工厂中,您可以对相关接口进行分组,因此,如果可以在工厂中对传递的参数进行分组,那么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。
简而言之,依赖注入与工厂方法分别意味着推入与拉动机制。
拉机制:类间接依赖于Factory方法,而工厂方法又依赖于具体类。
推动机构:根组件可以与所有从属组件一起配置在一个位置,从而提高了维护成本和松耦合。
使用Factory方法时,责任仍然属于类(尽管是间接地)以创建新对象,在该对象中,与依赖项注入一样,责任被外包(尽管以泄漏抽象为代价)
我认为这些是正交的,可以一起使用。让我向您展示一个我最近在工作中遇到的示例:
我们在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
构造函数。问题:
Parent
有Child
比应该更多的知识Parent
有更多的合作者Child
涉及更改Parent
这是我意识到一个Factory
完美适合的地方:
Child
该类的所有参数,除了真正的参数,Parent
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);
}
}
当您完全知道此时需要哪种类型的对象时,可以使用依赖项注入。在使用工厂模式的情况下,您只是将创建对象的过程委托给工厂,因为您不清楚所需的对象的确切类型。
我都使用这两种方法来创建“控制反转”策略,以使需要在我之后进行维护的开发人员更具可读性。
我使用Factory创建不同的Layer对象(业务,数据访问)。
ICarBusiness carBusiness = BusinessFactory.CreateCarBusiness();
另一个开发人员将看到这种情况,并且在创建业务层对象时,他会在BusinessFactory中查找,并且Intellisense为开发人员提供了所有可能的业务层创建。不必玩游戏,找到我要创建的界面。
此结构已经是控制反转。我不再负责创建特定对象。但是您仍然需要确保依赖注入能够轻松地进行更改。创建您自己的自定义Dependency Injection很荒谬,因此我使用Unity。在CreateCarBusiness()中,我要求Unity解析属于该类的类以及它的生命周期。
所以我的代码工厂依赖注入结构为:
public static class BusinessFactory
{
public static ICarBusiness CreateCarBusiness()
{
return Container.Resolve<ICarBusiness>();
}
}
现在我都受益。对于其他开发人员,我的代码对于我使用的对象的范围也更具可读性,而不是构造函数依赖注入(Instructor Dependency Injection),后者只是说创建类时每个对象都可用。
创建单元测试时,我用它来将数据库数据访问更改为自定义编码的数据访问层。我不希望我的单元测试与数据库,Web服务器,电子邮件服务器等进行通信。它们需要测试我的业务层,因为这就是智能所在。