[英]Factory method with DI and 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<ICar1>();
break;
case B:
return _container.Resolve<ICar2>();
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<Type, ICar> carFactory;
public CarFactory(Func<Type, ICar> carFactory)
{
this.carFactory = carFactory;
}
public ICar CreateCar(Type carType)
{
return carFactory(carType);
}
这是使用Unity DI容器的“ Composition Root
”的外观:
Func<Type, ICar> carFactoryFunc = type => (ICar)container.Resolve(type);
container.RegisterInstance<CarFactory>(new CarFactory(carFactoryFunc));
我前段时间回答了类似的问题。 基本上,一切都取决于您的选择。 您必须在详细程度(这可以为您提供来自编译器的更多帮助)和自动化之间进行选择,它可以使您编写更少的代码,但更容易出现错误。
这是我支持冗长的答案。
这也是支持自动化的一个很好的答案。
编辑
我相信您认为错误的方法实际上是最好的。 说实话,通常那里没有那么多的依赖关系。 我喜欢这种方法,因为它非常明确,很少会导致运行时错误。
替代方法1:
这个不好。 它实际上是一个服务定位器,被认为是反模式 。
替代方法2
如您所写,将它与IOC容器混合使用并不容易。 但是,在某些情况下,类似的方法( 穷人的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 ...
通过将参数列表封装在特定的类中,您只需使客户端确切地提供特定工厂方法调用所需的这些参数即可。
不幸的是,从您的帖子中还不清楚这些Dep1
...是什么以及如何使用它们。
我建议采用以下方法,然后将工厂提供者与实际工厂实施区分开来。 这种方法称为本地工厂模式:
public class CarFactory
{
private static Func<type, ICar> _provider;
public static void SetProvider( Func<type, ICar> provider )
{
_provider = provider;
}
public ICar CreateCar(type)
{
return _provider( type );
}
}
工厂本身没有任何实现,它是在此处为您的域API设置基础,在该域中,您仅希望使用此API创建汽车实例。
然后,在“合成根目录”(在配置实际容器的应用程序起点附近)中,配置提供程序:
CarFactory.SetProvider(
type =>
{
switch ( type )
{
case A:
return _container.Resolve<ICar1>();
case B:
return _container.Resolve<ICar2>();
..
}
);
请注意,工厂提供程序的此示例实现使用委托,但是接口也可以用作实际提供程序的规范。
从您编辑的问题来看,此实现基本上是#1,但是,它没有任何特殊的缺点。 客户仍然致电:
var car = new CarFactory().CreareCar( type );
我会考虑给依赖项提供一个良好的结构,以便您可以利用类似于Wiktor的答案的方法,但是我会抽象化Car工厂本身。 然后,您不使用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<TCar, TCarSpecs>
where TCar : ICar
where TCarSpecs : ICarSpecs
{
//Async cause everything non-trivial should be IMHO!
Task<TCar> 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<Van, IVanSpecs>
{
//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<Van> CreateCar(IVanSpecs carSpecs)
{
var van = new Van()
{
//create the actual implementation here.
};
//await something or other
return van;
}
}
我没有列出它,但是您现在可以实现多种类型的汽车及其相应的工厂,并使用DI注入所需的任何东西。
许多DI容器都支持命名依赖项的概念。
例如(Structuremap语法)
For<ICar>().Use<CarA>().Named("aCar");
Container.GetNamedInstance("aCar") // gives you a CarA instance
如果您使用类似约定的规则,即规则的名称是从具体的汽车类型本身派生的,那么您将遇到以下情况:扩展系统时,无需再接触工厂。
在工厂中使用它很简单。
class Factory(IContainer c) {
public ICar GetCar(string name) {
Return c.GetNamedInstance(name);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.