使用 IoC 容器的干净工厂设计模式






4.57/5 (8投票s)
编码原则
目标读者
本文期望读者对依赖倒置原则(DIP)和工厂设计模式有所了解。为了简化,代码没有进行防御性编程,也没有保护性语句。代码使用的是 Simple Injector,但所述原则也适用于其他 IoC 容器框架。
问题
如果在项目中实现了工厂类,而该项目使用了控制反转(IoC)容器,并且您遇到了下面描述的解决方案,那么本文就是为您准备的。
using System;
using DirtyFactory.Dependencies;
namespace DirtyFactory.Processors
{
internal class ProcessorFactory : IProcessorFactory
{
private readonly IDependencyOne _depOne;
private readonly IDependencyTwo _depTwo;
private readonly IDependencyThree _depThree;
public ProcessorFactory(IDependencyOne depOne, IDependencyTwo depTwo, IDependencyThree depThree)
{
_depOne = depOne;
_depTwo = depTwo;
_depThree = depThree;
}
public IProcessor Create(RequestType requestType)
{
switch(requestType)
{
case RequestType.Internal:
return new InternalProcessor(_depOne, _depTwo);
case RequestType.External:
return new ExternalProcessor(_depOne, _depThree);
default:
throw new NotImplementedException();
}
}
}
}
示例代码是一个处理器工厂类的实现,它包含一个名为 Create
的工厂方法和一个构造函数。
上述解决方案的主要问题是,工厂类通过其构造函数注入其处理器的依赖项。InternalProcessor
依赖于 IDependencyOne
和 IDependencyTwo
,而 ExternalProcessor
依赖于 IDependencyOne
和 IDependencyThree
。因此,工厂类依赖于 IDependencyOne
、IDependencyTwo
和 IDependencyThree
。另一个后果是,如果以后添加新的处理器,还需要在工厂类的构造函数中适应新处理器的依赖项。
以下是使用 Simple Injector 4.0.12 容器的主程序。代码通过构造函数注入应用了依赖倒置原则,并利用容器配置类组合(有关更多详细信息,请参阅 我的博客)。
using System;
using DirtyFactory.Dependencies;
using DirtyFactory.Processors;
using SimpleInjector;
namespace DirtyFactory
{
internal class Program
{
internal static IProcessorFactory _processorFactory;
static void Main(string[] args)
{
//1.register the container
Container container = GetRegisteredContainer();
//2.simulate the internal state of the program
_processorFactory = container.GetInstance<IProcessorFactory>();
//3.each of this request below simulate independant executing of the program
RunRequest(RequestType.Internal);
RunRequest(RequestType.External);
Console.ReadKey();
}
private static void RunRequest(RequestType requestType)
{
IProcessor internalProcessor = _processorFactory.Create(requestType);
Console.WriteLine(internalProcessor.GetResponse());
}
private static Container GetRegisteredContainer()
{
SimpleInjector.Container container = new SimpleInjector.Container();
container.Register<IDependencyOne, DependencyOne>();
container.Register<IDependencyTwo, DependencyTwo>();
container.Register<IDependencyThree, DependencyThree>();
container.Register<IProcessorFactory, ProcessorFactory>();
return container;
}
}
}
以下是其余代码
using DirtyFactory.Dependencies;
namespace DirtyFactory.Processors
{
internal enum RequestType
{
Internal,
External
}
internal interface IProcessorFactory
{
IProcessor Create(RequestType requestType);
}
internal interface IProcessor
{
string GetResponse();
}
internal class ExternalProcessor : IProcessor
{
private readonly IDependencyOne _depOne;
private readonly IDependencyThree _depThree;
public ExternalProcessor(IDependencyOne depOne, IDependencyThree depThree)
{
_depOne = depOne;
_depThree = depThree;
}
public string GetResponse()
{
return "External Response";
}
public bool IsUser(RequestType requestType)
{
return requestType == RequestType.External;
}
}
internal class InternalProcessor : IProcessor
{
private readonly IDependencyOne _depOne;
private readonly IDependencyTwo _depTwo;
public InternalProcessor(IDependencyOne depOne, IDependencyTwo depTwo)
{
_depOne = depOne;
_depTwo = depTwo;
}
public string GetResponse()
{
return "Internal Response";
}
public bool IsUser(RequestType requestType)
{
return requestType == RequestType.Internal;
}
}
}
namespace DirtyFactory.Dependencies
{
internal interface IDependencyOne
{
}
internal class DependencyOne : IDependencyOne
{
}
internal interface IDependencyTwo
{
}
internal class DependencyTwo : IDependencyTwo
{
}
internal interface IDependencyThree
{
}
internal class DependencyThree : IDependencyThree
{
}
}
为了简化解释,首先描述解决方案,然后讨论探索替代方案。
解决方案
上述问题的解决方案是将处理器推入工厂类的依赖项中。
然而,为了实现端到端工作,需要进行一系列更改。
- 工厂类需要注入一个
IProcessor
的集合。 - 先前在
factory
类中的切换逻辑,现在变成了集合查找。因此,每个处理器都需要有关它所服务的requestType
的信息。 - 容器需要注册项目中的所有
IProcessor
。
因此,如果添加新的处理器,工厂类和其他代码都无需更改,这是理想情况。
以下是工厂类的更改(粗斜体表示)
using System.Collections.Generic;
using System.Linq;
namespace CleanFactory.Processors
{
internal class ProcessorFactory : IProcessorFactory
{
private readonly IEnumerable<IProcessor> _processors;
public ProcessorFactory(IEnumerable<IProcessor> processors)
{
_processors = processors;
}
public IProcessor Create(RequestType requestType)
{
return _processors.Single(item => item.IsValidUser(requestType));
}
}
}
首先,IProcessor
的集合通过构造函数注入,形式为 IEnumerable
。实际上,可以使用哪个集合接口取决于 IoC 容器支持的内容。对于 Simple Injector,您可以传递 IList
、Array
、ICollection
、IReadOnlyCollection
或 IEnumerable
。
其次,switch
语句转换为 Create
方法内的集合查找。为了支持这一点,在 IProcessor
中添加了一个名为 IsValidUser
的额外方法,并且 IProcessor
的实现也因此发生了变化。
namespace CleanFactory.Processors
{
internal interface IProcessor
{
bool IsValidUser(RequestType requestType);
string GetResponse();
}
internal class InternalProcessor : IProcessor
{
private readonly IDependencyOne _depOne;
private readonly IDependencyTwo _depTwo;
public InternalProcessor(IDependencyOne depOne, IDependencyTwo depTwo)
{
_depOne = depOne;
_depTwo = depTwo;
}
public string GetResponse()
{
return "Internal Response";
}
public bool IsValidUser(RequestType requestType)
{
return requestType == RequestType.Internal;
}
}
internal class ExternalProcessor : IProcessor
{
private readonly IDependencyOne _depOne;
private readonly IDependencyThree _depThree;
public ExternalProcessor(IDependencyOne depOne, IDependencyThree depThree)
{
_depOne = depOne;
_depThree = depThree;
}
public string GetResponse()
{
return "External Response";
}
public bool IsValidUser(RequestType requestType)
{
return requestType == RequestType.External;
}
}
}
最后,容器需要注册项目中的所有处理器。
using System;
using System.Reflection;
using CleanFactory.Dependencies;
using CleanFactory.Processors;
using SimpleInjector;
namespace CleanFactory
{
internal class Program
{
internal static IProcessorFactory _processorFactory;
static void Main(string[] args)
{
//register the container
Container container = GetRegisteredContainer();
//simulate the internal state of the program
_processorFactory = container.GetInstance<IProcessorFactory>();
//each of this request below simulate independant executing of the program
RunRequest(RequestType.Internal);
RunRequest(RequestType.External);
//just to hold the program
Console.ReadKey();
}
private static void RunRequest(RequestType requestType)
{
IProcessor internalProcessor = _processorFactory.Create(requestType);
Console.WriteLine(internalProcessor.GetResponse());
}
private static Container GetRegisteredContainer()
{
SimpleInjector.Container container = new SimpleInjector.Container();
container.Register<IDependencyOne, DependencyOne>();
container.Register<IDependencyTwo, DependencyTwo>();
container.Register<IDependencyThree, DependencyThree>();
container.Register<IProcessorFactory, ProcessorFactory>();
container.RegisterCollection<IProcessor>
(new Assembly[] { Assembly.GetExecutingAssembly() });
return container;
}
}
}
Simple Injector 提供了多种集合注册方式,例如,指定具体实现类的数组或 IEnumerable
,或者指定程序集。如果指定了程序集,容器将执行反射来枚举程序集中的所有具体实现。
(其余代码无需其他更改。)
讨论
在 Stack Overflow 上有一些问题询问 IoC 容器是否会取代工厂设计模式。从我们在此学到的来看,工厂模式仍然可以与 IoC 容器并存。然而,在上述解决方案中,工厂模式的角色正在发生变化。工厂不再负责创建对象,而只是返回作为工厂依赖项注入的对象(从而降低了工厂的意义)。IoC 容器负责创建对象并控制其生命周期,但处理器对象在处理器工厂内的生命周期始终是“单例”,因为它们只从容器注入工厂类一次。
如果工厂类 intended to control the lifecycle of the processors,那么从容器注入的对象应该被视为处理器模板。通过这种方法,工厂可以通过克隆处理器模板来创建新对象(从而恢复工厂的意义)。
还有其他替代解决方案,即不将处理器集合,而是将容器本身传递给工厂类。这样做将允许容器控制工厂返回的处理器的生命周期。
先前解决方案的另一个方面是在 IProcessor
及其实现中添加了新的 IsValidUser
方法。这有不同的实现方式。如果切换逻辑基于单个实体,例如 enum
或基本类型,最简单的方法是将其实现为属性。使用方法可以为更复杂的条件检查提供灵活性,例如检查两个或多个参数。因此,方法方式在某种程度上更通用。
也可以不使用处理器中的额外方法,而是实现其他形式的映射,例如,使用 requestType
上的属性,甚至将映射视为工厂类的附加依赖项。如果您有兴趣进一步探讨这个问题,请随时留下评论。