使用DI和IoC的工厂方法

我熟悉这些模式,但仍然不知道如何处理以下情况:

public class CarFactory { public CarFactory(Dep1,Dep2,Dep3,Dep4,Dep5,Dep6) { } public ICar CreateCar(type) { switch(type) { case A: return new Car1(Dep1,Dep2,Dep3); break; case B: return new Car2(Dep4,Dep5,Dep6); break; } } } 

通常,问题在于需要注入的引用量。 当有更多的汽车时会更糟。

我想到的第一种方法是在工厂构造函数中注入Car1和Car2,但它违反工厂方法,因为工厂将始终返回相同的对象。 第二种方法是注入servicelocator但它的反模式到处都是。 怎么解决?

编辑:

替代方式1:

 public class CarFactory { public CarFactory(IContainer container) { _container = container; } public ICar CreateCar(type) { switch(type) { case A: return _container.Resolve(); break; case B: return _container.Resolve(); break; } } } 

替代方式2(由于树中的依赖性过多而难以使用):

 public class CarFactory { public CarFactory() { } public ICar CreateCar(type) { switch(type) { case A: return new Car1(new Dep1(),new Dep2(new Dep683(),new Dep684()),....) break; case B: return new Car2(new Dep4(),new Dep5(new Dep777(),new Dep684()),....) break; } } } 

在工厂内部有一个switch case语句是代码味道。 有趣的是,你似乎根本没有专注于解决这个问题。

对于这种情况,最好的,最友好的DI解决方案是策略模式 。 它允许您的DI容器将依赖项注入它们所属的工厂实例,而不会使具有这些依赖项的其他类混乱或诉诸服务定位器。

接口

 public interface ICarFactory { ICar CreateCar(); bool AppliesTo(Type type); } public interface ICarStrategy { ICar CreateCar(Type type); } 

工厂

 public class Car1Factory : ICarFactory { private readonly IDep1 dep1; private readonly IDep2 dep2; private readonly IDep3 dep3; public Car1Factory(IDep1 dep1, IDep2 dep2, IDep3 dep3) { if (dep1 == null) throw new ArgumentNullException("dep1"); if (dep2 == null) throw new ArgumentNullException("dep2"); if (dep3 == null) throw new ArgumentNullException("dep3"); this.dep1 = dep1; this.dep2 = dep2; this.dep3 = dep3; } public ICar CreateCar() { return new Car1(this.dep1, this.dep2, this.dep3); } public bool AppliesTo(Type type) { return typeof(Car1).Equals(type); } } public class Car2Factory : ICarFactory { private readonly IDep4 dep4; private readonly IDep5 dep5; private readonly IDep6 dep6; public Car1Factory(IDep4 dep4, IDep5 dep5, IDep6 dep6) { if (dep4 == null) throw new ArgumentNullException("dep4"); if (dep5 == null) throw new ArgumentNullException("dep5"); if (dep6 == null) throw new ArgumentNullException("dep6"); this.dep4 = dep4; this.dep5 = dep5; this.dep6 = dep6; } public ICar CreateCar() { return new Car2(this.dep4, this.dep5, this.dep6); } public bool AppliesTo(Type type) { return typeof(Car2).Equals(type); } } 

战略

 public class CarStrategy : ICarStrategy { private readonly ICarFactory[] carFactories; public CarStrategy(ICarFactory[] carFactories) { if (carFactories == null) throw new ArgumentNullException("carFactories"); this.carFactories = carFactories; } public ICar CreateCar(Type type) { var carFactory = this.carFactories .FirstOrDefault(factory => factory.AppliesTo(type)); if (carFactory == null) { throw new Exception("type not registered"); } return carFactory.CreateCar(); } } 

用法

 // I am showing this in code, but you would normally // do this with your DI container in your composition // root, and the instance would be created by injecting // it somewhere. var strategy = new CarStrategy(new ICarFactory[] { new Car1Factory(dep1, dep2, dep3), new Car2Factory(dep4, dep5, dep6) }); // And then once it is injected, you would simply do this. // Note that you could use a magic string or some other // data type as the parameter if you prefer. var car1 = strategy.CreateCar(typeof(Car1)); var car2 = strategy.CreateCar(typeof(Car2)); 

请注意,由于没有switch case语句,您可以在不更改设计的情况下向策略添加其他工厂,并且每个工厂都可以拥有自己的DI容器注入的依赖项。

 var strategy = new CarStrategy(new ICarFactory[] { new Car1Factory(dep1, dep2, dep3), new Car2Factory(dep4, dep5, dep6), new Car3Factory(dep7, dep8, dep9) }); var car1 = strategy.CreateCar(typeof(Car1)); var car2 = strategy.CreateCar(typeof(Car2)); var car3 = strategy.CreateCar(typeof(Car3)); 

使用Composition Root汇总您对代码示例的评论。 您可以创建以下内容,但这不是服务定位器。

 public class CarFactory { private readonly Func carFactory; public CarFactory(Func carFactory) { this.carFactory = carFactory; } public ICar CreateCar(Type carType) { return carFactory(carType); } 

这就是使用Unity DI容器查看Composition Root方法:

 Func carFactoryFunc = type => (ICar)container.Resolve(type); container.RegisterInstance(new CarFactory(carFactoryFunc)); 

我前段时间回答了类似的问题。 基本上这都是你的选择。 您必须在详细程度(这为编译器提供更多帮助)和自动化之间做出选择,这允许您编写更少的代码但更容易出错。

这是我支持冗长的答案。

这也是支持自动化的好答案。

编辑

我相信你认为错误的方法实际上是最好的。 说实话,通常没有那么多依赖。 我喜欢这种方法,因为它非常明确,很少导致运行时错误。

替代方式1:

这个很糟糕。 它实际上是一个服务定位器,被认为是反模式 。

替代方式2

就像你写的那样,如果与IOC包含混合使用它并不容易。 然而,在某些情况下,类似的方法( 穷人的DI )可能是有用的。

总而言之,我不打算在你的工厂中拥有“很多”依赖关系。 这是一个简单的声明性代码。 写入需要几秒钟,可以节省您在运行时错误中挣扎的时间。

我会考虑给依赖项一个很好的结构,这样你就可以使用类似于Wiktor的答案,但我会抽象出汽车工厂本身。 然后,您不使用if..then结构。

 public interface ICar { string Make { get; set; } string ModelNumber { get; set; } IBody Body { get; set; } //IEngine Engine { get; set; } //More aspects...etc. } public interface IBody { //IDoor DoorA { get; set; } //IDoor DoorB { get; set; } //etc } //Group the various specs public interface IBodySpecs { //int NumberOfDoors { get; set; } //int NumberOfWindows { get; set; } //string Color { get; set; } } public interface ICarSpecs { IBodySpecs BodySpecs { get; set; } //IEngineSpecs EngineSpecs { get; set; } //etc. } public interface ICarFactory where TCar : ICar where TCarSpecs : ICarSpecs { //Async cause everything non-trivial should be IMHO! Task CreateCar(TCarSpecs carSpecs); //Instead of having dependencies ctor-injected or method-injected //Now, you aren't dealing with complex overloads IService1 Service1 { get; set; } IBuilder1 Builder1 { get; set; } } public class BaseCar : ICar { public string Make { get; set; } public string ModelNumber { get; set; } public IBody Body { get; set; } //public IEngine Engine { get; set; } } public class Van : BaseCar { public string VanStyle { get; set; } //etc. } public interface IVanSpecs : ICarSpecs { string VanStyle { get; set; } } public class VanFactory : ICarFactory { //Since you are talking of such a huge number of dependencies, //it may behoove you to properly categorize if they are car or //car factory dependencies //These are injected in the factory itself public IBuilder1 Builder1 { get; set; } public IService1 Service1 { get; set; } public async Task CreateCar(IVanSpecs carSpecs) { var van = new Van() { //create the actual implementation here. }; //await something or other return van; } } 

我没有列出它,但你现在可以实现多种类型的汽车及其相应的工厂,并使用DI来注入你需要的任何东西。

首先,你有一个具体的工厂,一个IoC容器可能是一个替代,而不是帮助你的东西。

然后,只需重构工厂,不要期望工厂构造函数中有完整的可能参数列表。 这是主要问题 – 如果工厂方法不需要它们,为什么要传递这么多参数?

我宁愿将特定参数传递给工厂方法

 public abstract class CarFactoryParams { } public class Car1FactoryParams : CarFactoryParams { public Car1FactoryParams(Dep1, Dep2, Dep3) { this.Dep1 = Dep1; ... } public class Car2FactoryParams ... public class CarFactory { public ICar CreateCar( CarFactoryParams params ) { if ( params is Car1FactoryParams ) { var cp = (Car1FactoryParams)params; return new Car1( cp.Dep1, cp.Dep2, ... ); } ... if ( params is ... 

通过将参数列表封装在特定的类中,您只需使客户端提供特定工厂方法调用所需的这些参数。

编辑:

不幸的是,从你的post中不清楚这些Dep1是什么,……以及你如何使用它们。

我建议采用以下方法将工厂提供商与实际工厂实施区分开来。 这种方法称为本地工厂模式:

 public class CarFactory { private static Func _provider; public static void SetProvider( Func provider ) { _provider = provider; } public ICar CreateCar(type) { return _provider( type ); } } 

工厂本身没有任何实现,它是为您的域API设置基础,您希望仅使用此API创建汽车实例。

然后,在Composition Root(在您配置实际容器的app的起点附近),配置提供程序:

 CarFactory.SetProvider( type => { switch ( type ) { case A: return _container.Resolve(); case B: return _container.Resolve(); .. } ); 

请注意,工厂提供程序的此示例实现使用委托,但接口也可以用作实际提供程序的规范。

这个实现基本上是您编辑的问题中的第一个,但它没有任何特别的缺点。 客户仍然致电:

 var car = new CarFactory().CreareCar( type ); 

许多DI容器支持命名依赖项的概念。

例如(Structuremap语法)

 For().Use().Named("aCar"); Container.GetNamedInstance("aCar") // gives you a CarA instance 

如果你使用类似约定的东西,规则如何从混凝土汽车类型本身得出名称,那么你有一种情况,当你扩展系统时你不再需要触摸工厂了。

在工厂中使用它很简单。

 class Factory(IContainer c) { public ICar GetCar(string name) { Return c.GetNamedInstance(name); } }