依赖注入与工厂模式

Bin*_*ony 484 design-patterns dependency-injection factory-pattern

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

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

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

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

wil*_*ood 285

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

  • DI模式不需要任何框架.您可以通过手动编写DI的工厂来进行DI.DI框架让它变得更容易. (171认同)
  • @Perpetualcoder - 谢谢@Esko - 不要意识到一个高级的第三方库这个词框架. (27认同)
  • 我没有获得改变1行XML与改变1行代码的优势.你能详细说说吗? (5认同)
  • +1 @willcode谢谢!所以你必须改变config/xml文件.我知道了.维基链接http://en.wikipedia.org/wiki/Dependency_injection#Manually-injected_dependency将工厂模式定义为"手动注入的依赖关系" (4认同)
  • 重复 OP 答案,用“工厂”替换“DI”,仍然有意义。 (2认同)

Per*_*der 215

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

  • 确实,Factory Pattern是实现依赖注入的方式之一.使用依赖注入对工厂模式的另一个好处是DI框架可以让您灵活地根据具体类型注册抽象.例如Code as Config,XML或Auto Configuration.这种灵活性可以让您管理对象的生命周期或决定注册接口的方式和时间. (4认同)
  • 工厂模式提供了比 DI 更高级别的抽象。工厂可以像 DI 一样提供配置选项,但它也可以选择隐藏所有这些配置细节,以便工厂可以在客户端不知道的情况下决定切换到完全不同的实现。DI 本身不允许这样做。DI 要求客户指定他想要的更改。 (2认同)
  • 我认为工厂模式是实现IoC(而不是DI)的工具更准确.请注意[DI是IoC的一种形式](/sf/ask/458549031/) (2认同)

Des*_*tar 177

依赖注入

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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;
    }   
}
Run Code Online (Sandbox Code Playgroud)

结果

如您所见,工厂和DI相互补充.

static void Main()
{
     ICar car = CarFactory.BuildCar();
     // use car
}
Run Code Online (Sandbox Code Playgroud)

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

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();
}
Run Code Online (Sandbox Code Playgroud)

示例#1 - 这是最糟糕的,因为它完全隐藏了依赖性.如果你把方法视为一个黑盒子,你根本不知道它需要一辆车.

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

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

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

  • @PhilGoetz您所描述的内容听起来更像是服务定位器模式.他们有类似的目标,因为他们的目标是将服务与消费者分离.但是,服务定位器模式有许多缺点.主要是,隐藏依赖关系不是一件好事.消费者仍然需要获得它的依赖关系,但它们不是定义一个清晰的接口,而是"秘密地"传递并依赖于全局状态.在这个例子中,消费者不知道工厂,它只询问它需要什么,而不必关心如何构建这种需求. (7认同)
  • 这不是DI模式,这只是IoC的一个实现.DI使用ServiceLocator确定正确的依赖关系并在对象创建期间将其注入构造函数中,您的代码仅隔离对象范围内对象的创建. (3认同)
  • @DiegoMendes您所描述的是一个自动化DI的框架.你称之为ServiceLocator为你做DI.我所展示的是模式本身,不需要任何花哨的框架.请参阅http://stackoverflow.com/a/140655/1160036,或参见https://en.wikipedia.org/wiki/Dependency_injection#Dependency_injection_frameworks:"[框架列表]支持依赖注入,但不需要执行依赖注入".此外,"注入是将依赖关系传递给依赖对象".你复杂化了一个非常简单的概念. (3认同)
  • 工厂模式作为 DI 注入。 (2认同)
  • @MatthewWhited 大多数设计决策都需要权衡。通过 DI,您可以获得灵活性、透明度和更易于测试的系统,但代价是暴露您的胆量。许多人认为这是一个可以接受的权衡。这并不是说 DI 始终是最佳解决方案,并且这个问题与此无关。该示例旨在通过展示 DI 和工厂模式的一些好的和坏的用法来展示它们之间的区别。 (2认同)

Tom*_*her 45

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

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

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

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

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

  • 这是最好的答案……其他答案要么不提及IoC,要么不认识DI是IoC的一种形式。 (3认同)
  • @Faisal我认为这是一个很好的方法。DI 是 ASP.NET Core 等现代框架所青睐的技术。如前所述,永远不会有一种适合所有类型的解决方案。总会有一个用例需要您以某种方式使用工厂模式。正如您将看到的,大多数 DI 框架都支持以某种格式使用工厂模式。因此,如果您确实需要它们,它们可以作为扩展点。 (2认同)

yfe*_*lum 40

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

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

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

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


小智 25

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


Pup*_*Pup 22

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

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


小智 16

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

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

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


Mar*_*zak 14

理论

有两个要点需要考虑:

  1. 谁创造了对象

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

    • [工厂]:通常负责创建有状态对象
    • [依赖注入]更有可能创建无状态对象

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

  1. 我们想要建立什么

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

  1. 建筑

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

在此输入图像描述

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

  1. 域层和工厂的使用

将在数据库中的实体是Order和OrderLine.订单可以有多个OrderLines. Order和OrderLine之间的关系

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

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

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

在此输入图像描述

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

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

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

在此输入图像描述

  • 有状态对象可能位于应用程序的域层中,在该域中,您将数据映射到数据库时,通常不会在其中使用依赖项注入。工厂常用于从复杂对象的树中创建AggregateRoot (2认同)

Fra*_*ana 12

我知道这个问题已经过时但我想补充五美分,

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

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

MyBean mb = ctx.getBean("myBean");
Run Code Online (Sandbox Code Playgroud)

然后使用'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
Run Code Online (Sandbox Code Playgroud)

与此相同:

@Autowired MyBean mbForEnterprise1; //In the classes of the first enterprise
@Autowired MyBean mbForEnterprise2; //In the classes of the second enterprise
Run Code Online (Sandbox Code Playgroud)

还有这个:

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
Run Code Online (Sandbox Code Playgroud)

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

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

MyBean mb = (MyBean)MyFactory.get("mb"); 
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  • 太多的开发人员认为创建依赖注入框架是为了带来新的东西.正如您所解释的那样,传统工厂(通常是抽象工厂)可以扮演相同的依赖倒置角色.反转与注射?对我来说,依赖注入框架的唯一"好处"是,当添加/更改依赖项时,我们不必更改/重新编译代码(因为大多数情况下可以使用像Spring这样的XML进行配置).为什么要避免重新编译?为了避免在改变依赖/工厂时出现一些潜在的人为错误.但是通过我们出色的IDE,重构效果很好:) (3认同)

Tha*_*kur 6

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


Kos*_*801 5

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

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

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

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

  • 你所描述的不是DI。您谈到了 DI freamwork,它使您在编译时重写代码变得繁重。他自己的模式与您所描述的没有任何联系 (2认同)