带有 DI 和 IoC 的工厂方法
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,但它反对工厂方法,因为工厂将 return 始终是同一个对象。第二种方法是注入 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;
}
}
}
首先,你有一个具体的工厂,一个 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 ...
通过将参数列表封装在特定的 class 中,您只需让客户端提供特定工厂方法调用所需的这些参数。
编辑:
不幸的是,从您的 post 中并不清楚这些 Dep1
是什么,...以及您如何使用它们。
我建议采用以下方法将工厂提供者与实际工厂实施分开。这种方法被称为 Local Factory 模式:
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>();
..
}
);
请注意,工厂提供者的此示例实现使用委托,但接口也可用作实际提供者的规范。
此实现基本上是您编辑的问题中的第一名,但是,它没有任何特别的缺点。客户端仍然调用:
var car = new CarFactory().CreareCar( type );
我会考虑为依赖项提供一个良好的结构,以便您可以使用类似于 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<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 注入任何你需要的东西。
前段时间我回答过类似的问题。基本上这完全取决于您的选择。您必须在冗长(编译器为您提供更多帮助)和自动化之间做出选择,自动化允许您编写更少的代码,但更容易出现错误。
是我支持冗长的回答。
而也是支持自动化的好答案
编辑
我相信你认为错误的方法实际上是最好的。说实话,通常那里不会有 so 很多依赖项。我喜欢这种方法,因为它非常明确并且很少导致运行时错误。
Alternative way 1:
这个不好。它实际上是一个服务定位器,被认为是 anti-pattern.
Alternative way 2
就像你写的那样,如果和IOC容器混合使用就不好用了。然而,在某些情况下,类似的方法 (poor man's DI) 可能会有用。
总而言之,我不介意在你们的工厂中有 "many" 依赖项。这是一个简单的声明性代码。编写只需几秒钟,可以节省您与运行时错误斗争的时间。
许多 DI 容器支持命名依赖项的概念。
例如(结构图语法)
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);
}
}
用 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);
}
这就是你的 Composition Root
使用 Unity DI 容器的样子:
Func<Type, ICar> carFactoryFunc = type => (ICar)container.Resolve(type);
container.RegisterInstance<CarFactory>(new CarFactory(carFactoryFunc));
在工厂内部使用 switch case 语句是一种代码味道。有趣的是,你似乎根本没有专注于解决那个问题。
这种情况下最好、最友好的 DI 解决方案是 strategy pattern。它允许您的 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)
{
this.dep1 = dep1 ?? throw new ArgumentNullException(nameof(dep1));
this.dep2 = dep2 ?? throw new ArgumentNullException(nameof(dep2));
this.dep3 = dep3 ?? throw new ArgumentNullException(nameof(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 Car2Factory(IDep4 dep4, IDep5 dep5, IDep6 dep6)
{
this.dep4 = dep4 ?? throw new ArgumentNullException(nameof(dep4));
this.dep5 = dep5 ?? throw new ArgumentNullException(nameof(dep5));
this.dep6 = dep6 ?? throw new ArgumentNullException(nameof(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)
{
this.carFactories = carFactories ?? throw new ArgumentNullException(nameof(carFactories));
}
public ICar CreateCar(Type type)
{
var carFactory = this.carFactories
.FirstOrDefault(factory => factory.AppliesTo(type));
if (carFactory == null)
{
throw new InvalidOperationException($"{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));
我熟悉这些模式,但仍然不知道如何处理以下情况:
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,但它反对工厂方法,因为工厂将 return 始终是同一个对象。第二种方法是注入 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;
}
}
}
首先,你有一个具体的工厂,一个 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 ...
通过将参数列表封装在特定的 class 中,您只需让客户端提供特定工厂方法调用所需的这些参数。
编辑:
不幸的是,从您的 post 中并不清楚这些 Dep1
是什么,...以及您如何使用它们。
我建议采用以下方法将工厂提供者与实际工厂实施分开。这种方法被称为 Local Factory 模式:
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>();
..
}
);
请注意,工厂提供者的此示例实现使用委托,但接口也可用作实际提供者的规范。
此实现基本上是您编辑的问题中的第一名,但是,它没有任何特别的缺点。客户端仍然调用:
var car = new CarFactory().CreareCar( type );
我会考虑为依赖项提供一个良好的结构,以便您可以使用类似于 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<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 注入任何你需要的东西。
前段时间我回答过类似的问题。基本上这完全取决于您的选择。您必须在冗长(编译器为您提供更多帮助)和自动化之间做出选择,自动化允许您编写更少的代码,但更容易出现错误。
而
编辑
我相信你认为错误的方法实际上是最好的。说实话,通常那里不会有 so 很多依赖项。我喜欢这种方法,因为它非常明确并且很少导致运行时错误。
Alternative way 1:
这个不好。它实际上是一个服务定位器,被认为是 anti-pattern.
Alternative way 2
就像你写的那样,如果和IOC容器混合使用就不好用了。然而,在某些情况下,类似的方法 (poor man's DI) 可能会有用。
总而言之,我不介意在你们的工厂中有 "many" 依赖项。这是一个简单的声明性代码。编写只需几秒钟,可以节省您与运行时错误斗争的时间。
许多 DI 容器支持命名依赖项的概念。
例如(结构图语法)
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);
}
}
用 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);
}
这就是你的 Composition Root
使用 Unity DI 容器的样子:
Func<Type, ICar> carFactoryFunc = type => (ICar)container.Resolve(type);
container.RegisterInstance<CarFactory>(new CarFactory(carFactoryFunc));
在工厂内部使用 switch case 语句是一种代码味道。有趣的是,你似乎根本没有专注于解决那个问题。
这种情况下最好、最友好的 DI 解决方案是 strategy pattern。它允许您的 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)
{
this.dep1 = dep1 ?? throw new ArgumentNullException(nameof(dep1));
this.dep2 = dep2 ?? throw new ArgumentNullException(nameof(dep2));
this.dep3 = dep3 ?? throw new ArgumentNullException(nameof(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 Car2Factory(IDep4 dep4, IDep5 dep5, IDep6 dep6)
{
this.dep4 = dep4 ?? throw new ArgumentNullException(nameof(dep4));
this.dep5 = dep5 ?? throw new ArgumentNullException(nameof(dep5));
this.dep6 = dep6 ?? throw new ArgumentNullException(nameof(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)
{
this.carFactories = carFactories ?? throw new ArgumentNullException(nameof(carFactories));
}
public ICar CreateCar(Type type)
{
var carFactory = this.carFactories
.FirstOrDefault(factory => factory.AppliesTo(type));
if (carFactory == null)
{
throw new InvalidOperationException($"{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));