引用的大多数使用依赖注入的例子,我们也可以使用工厂模式来解决。看起来当涉及到使用/设计时,依赖注入和工厂之间的区别是模糊或稀薄的。

曾经有人告诉我,你如何使用它才会有所不同!

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

谁能告诉我它们之间的区别在哪里使用什么,这里的最佳实践是什么?


当前回答

我知道这个问题很老了,但我想补充一下我的观点,

我认为依赖注入(DI)在很多方面类似于可配置的工厂模式(FP),从这个意义上说,你可以用DI做任何事情,你也可以用这样的工厂来做。

实际上,如果你使用spring为例,你可以选择自动装配资源(DI)或做这样的事情:

MyBean mb = ctx.getBean("myBean");

然后使用'mb'实例来做任何事情。这不是一个对工厂的调用,它将返回一个实例吗??

我注意到的大多数FP示例之间唯一真正的区别是,您可以在xml或其他类中配置“myBean”是什么,框架将作为工厂工作,但除此之外是一样的事情,您当然可以有一个工厂来读取配置文件或根据需要获得实现。

如果你问我的意见(我知道你没有),我相信DI做了同样的事情,但只是增加了开发的复杂性,为什么?

嗯,首先,为了让您知道用于DI自动装配的任何bean的实现是什么,您必须进入配置本身。

但是…您不必知道正在使用的对象的实现,这种承诺又如何呢?啐!严重吗?当你使用这样的方法时……你不就是写实现的那个人吗??即使你没有,你不是几乎所有的时间都在看如何实现它应该做什么??

最后一点,不管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,并在运行时设置规则(或者如果您保留此选项,则添加一个新的配置文件),无需更改现有类。这将是一个动态工厂!

这不是比为每个企业编写两个配置,甚至为每个企业编写两个不同的应用程序更有帮助吗?

You can tell me, I don't need to do the switch at runtime ever, so I configure the app, and if I inherit the class or use another implementation I just change the config and redeploy. Ok, that can also be done with a factory. And be honest, how many times do you do this? maybe only when you have an app that's going to be used somewhere else in your company, and you are going to pass the code to another team, and they will do things like this. But hey, that can also be done with the factory, and would be even better with a dynamic factory!!

不管怎样,评论区是开放的,你可以杀了我。

其他回答

DI为您提供了一个组合根,这是连接对象图的一个集中位置。这往往使对象依赖关系非常显式,因为对象确切地要求它们所需要的东西,并且只有一个地方可以得到它。

组合根是一种清晰而直接的关注点分离。被注入的对象应该不依赖于DI机制,无论是第三方容器还是DIY DI。DI应该是不可见的。

工厂往往更加分散。不同的对象使用不同的工厂,工厂表示对象与其实际依赖关系之间的额外间接层。这个附加层将自己的依赖项添加到对象图中。工厂不是看不见的。工厂是一个中间商。

因此,更新工厂的问题更大:因为工厂是业务逻辑的依赖项,修改它们可能会产生连锁反应。组合根不是业务逻辑的依赖项,因此可以单独修改它。

GoF提到了更新抽象工厂的困难。他们的部分解释被引用在这里的回答中。将DI与工厂进行对比也与ServiceLocator是否是反模式这个问题有很多相似之处。

最终,选择哪个答案可能是固执己见的;但我认为这可以归结为一个工厂是一个中间人。问题在于,除了提供产品之外,这个中间商是否还能通过增加额外价值来发挥自己的作用。因为如果你能在没有中间商的情况下得到同样的产品,那为什么不把中间商去掉呢?

一个图表有助于说明其中的区别。

依赖注入

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

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——这样会好一点,因为我们经过了一家汽车厂,现在我们知道我们需要一辆车。但是这次我们传递的太多了,因为这个方法实际上只需要一个car。我们正在路过一个工厂,只是为了建造汽车,当汽车可以在外面建造的方法和通过。

示例#3—这是理想的,因为该方法要求的正是它所需要的。不要太多也不要太少。我不需要为了创建MockCars而编写MockCarFactory,我可以直接传入mock。它是直接的,界面不会说谎。

Misko Hevery的谷歌技术演讲非常棒,这是我得到我的例子的基础。http://www.youtube.com/watch?v=XcT4yYu_TTs

我的想法:

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

我认为它们是正交的,可以一起使用。让我给你看一个我最近在工作中遇到的例子:

我们使用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的了解比它应该了解的要多 母公司的合作者太多了 向Child添加依赖项需要更改Parent

这时我意识到工厂非常适合这里:

它隐藏了Child类的所有真实参数,就像Parent所看到的那样 它封装了创建子节点的知识,这些知识可以集中在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);
    }
}

我相信DI是一种配置或即时化bean的方法。DI可以通过很多方式来实现,比如构造函数,setter-getter等等。

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

检查这个链接:依赖注入