有人曾经告诉我,它的不同在于你如何使用它!
我曾经使用StructureMap作为DI容器来解决问题,后来重新设计为使用简单的工厂,并删除了对StructureMap的引用。
有人能告诉我它们之间的区别以及在何处使用什么,这里最好的实践是什么?
我建议保持概念的简单明了。依赖注入更多地是一种用于松耦合软件组件的架构模式。工厂模式只是将创建其他类对象的责任分离到另一个实体的一种方式。工厂模式可以被称为实现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;
}
}
结果
正如你所看到的,工厂和依赖注入相互补充。
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 - 这比较好,因为现在我们知道需要一辆车,因为我们传入了一个汽车工厂。但这次我们传递了太多信息,因为实际上这个方法只需要一辆车。我们传入工厂只是为了在方法内部建造汽车,而这辆汽车可以在方法外部建造并传递进来。
示例 #3 - 这是理想的,因为该方法请求 恰好 需要的东西,既不多也不少。我不需要编写 MockCarFactory 来创建 MockCars,我可以直接传递模拟对象。它是直接的,接口没有欺骗性。
这个由 Misko Hevery 做的 Google Tech Talk 很棒,它是我从中得出示例的基础。http://www.youtube.com/watch?v=XcT4yYu_TTs
在某些情况下,使用依赖注入可以轻松解决问题,而使用一套工厂可能就不那么容易了。
控制反转和依赖注入(IOC/DI)与服务定位器或一套工厂(Factory)之间的区别在于:
IOC/DI本身是一个完整的领域对象和服务生态系统。它按照您指定的方式为您设置所有内容。您的领域对象和服务由容器构建,而不是自己构建:因此它们没有任何对容器或任何工厂的依赖性。IOC/DI允许高度可配置性,在应用程序的最上层(GUI、Web前端)的单个位置(容器的构建)中进行所有配置。
而工厂抽象出一些领域对象和服务的构造过程。但领域对象和服务仍然需要自行确定如何构建自己以及如何获取它们所依赖的所有内容。所有这些“主动”依赖关系会贯穿您应用程序的所有层。因此,没有单个位置可以去配置所有内容。
依赖注入的一个缺点是无法使用逻辑来初始化对象。例如,当我需要创建一个具有随机名称和年龄的角色时,依赖注入不如工厂模式。通过工厂模式,我们可以轻松地将随机算法从对象创建中封装起来,这支持了名为“封装变化”的一种设计模式。
需要考虑两个重要点:
用于创建包含多个订单行条目的订单的应用程序模块。
假设我们想要创建以下分层架构:
域对象可能是存储在数据库中的对象。
Repository(DAO)有助于从数据库中检索对象。
Service为其他模块提供API。允许对order
模块进行操作。
将在数据库中的实体是Order和OrderLine。订单可以有多个OrderLines。
现在来到重要的设计部分。模块外部的类是否应该自己创建和管理OrderLines?不应该。只有当您与订单相关联时,Order Line才应存在。最好隐藏内部实现以外部类。
但是如何在不了解OrderLines的情况下创建订单呢?
工厂模式
想要创建新订单的人使用OrderFactory(它会隐藏关于如何创建订单的细节)。
这是在IDE内部的样子。位于domain包之外的类将使用OrderFactory而不是Order内部的构造函数。
OrderRepository和OrderService由依赖注入框架管理。 Repository负责管理数据库上的CRUD操作。 Service注入Repository并使用它来保存/查找正确的domain类。
[工厂]:通常负责创建有状态的对象 [依赖注入] 更可能创建无状态的对象
我不明白为什么。 - Maksim Dmitriev我认为DI是工厂的一种抽象层,但它们提供了超越抽象的好处。一个真正的工厂知道如何实例化一个单一类型并进行配置。一个良好的DI层通过配置提供了能力,可以实例化和配置许多类型。
显然,对于一个包含几个简单类型的项目,在它们的构建中需要相对稳定的业务逻辑,工厂模式易于理解、实现并且效果良好。
另一方面,如果您有一个包含许多类型的项目,它们的实现经常会发生变化,DI通过其配置为您提供了灵活性,无需重新编译您的工厂即可在运行时进行更改。
我知道这个问题很老,但我想要补充我的意见,
我认为依赖注入(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文件,其中包含新类,并设置规则,甚至可以在运行时进行设置(或者如果您留下此选项,则添加新的配置文件),无需更改现有类。 这将是一个动态工厂!
这难道不比为每个企业编写两个配置文件,甚至每个企业都有两个不同的应用程序更有帮助吗?
你可以告诉我,我永远不需要在运行时切换,所以我配置应用程序,如果我继承该类或使用其他实现,我只需更改配置并重新部署即可。 好吧,这也可以通过工厂完成。 老实说,你有多少次这样做? 也许只有当您有一个将在公司中的其他地方使用的应用程序时,您才会执行此操作,然后您将传递代码给另一个团队,他们会执行此类操作。 但是嘿,这也可以通过工厂完成,并且使用动态工厂会更好!!
无论如何,评论区开放供您谴责。