目录
观众
问题
解决方案
讨论
Download source code - 1.7 MB
(译者注:Demo中CleanFactory为新解决方案,DirtyFactory为问题中提到的解决方案)
观众本文希望观众熟悉依赖倒置原则(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();
//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();
container.Register();
container.Register();
container.Register();
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 _processors;
public ProcessorFactory(IEnumerable processors)
{
_processors = processors;
}
public IProcessor Create(RequestType requestType)
{
return _processors.Single(item => item.IsValidUser(requestType));
}
}
}
首先, IProcessor的集合以IEnumerable的形式通过其构造函数注入。实际上,可以使用的集合接口取决于IoC容器中支持的内容。对于简单的注入器,你可以通过IList,Array,ICollection,IReadOnlyCollection,或IEnumerable。
其次,switch语句转换为Create方法内的集合查找。为了支持这一点,一个额外的方法,名为IsValidUser被添加到IProcessor中,并且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();
//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();
container.Register();
container.Register();
container.Register();
container.RegisterCollection
(new Assembly[] { Assembly.GetExecutingAssembly() });
return container;
}
}
}
简单注入器(Simple Injector)提供了多种方法进行集合注册,例如,指定数组或具体实现的IEnumerable或程序集。如果指定了程序集,则容器执行反射以枚举程序集中的所有具体实现。
(其余代码不需要进行其他更改。)
讨论在stack overflow中有很多问题都是询问IoC容器是否正在替换工厂设计模式。根据我们在这里学到的,工厂模式仍然可以与IoC容器并排使用。然而,工厂模式的角色在上述解决方案中正在发生变化。工厂不再负责创建对象,而只返回作为工厂依赖项注入的对象(因此减少了工厂的含义)。IoC容器负责创建对象并控制它们的生命周期,但处理器工厂内处理器对象的生命周期始终是“单例”,因为它们只从容器注入一次到工厂类。
如果工厂类打算控制处理器的生命周期,那么从容器注入的对象应该被当作处理器模板。通过这种方法,工厂可以通过克隆处理器模板来创建新对象(从而回收工厂的含义)。
还有其他替代解决方案,即将容器而不是处理器集合传递到工厂类中。这样做将允许容器控制工厂返回的处理器的生命周期。
前面描述的解决方案的另一个方面是加入的新方法,在IProcessor中的IsValidUser和它的实现。这有不同的特性。如果切换逻辑基于单个实体,例如enum或原始类型(如int),则最简单的方法是将其实现为属性。使用方法为更复杂的条件检查提供了灵活性,例如,两个或更多个参数检查。因此,方法的方法在某种程度上是更通用的。
也可以不在处理器中使用额外的方法,而是实现其他形式的映射,例如,在上requestType使用属性,甚至将映射视为对工厂类的附加依赖。如果您有兴趣进一步探索,请给我一些评论。
原文地址:https://www.codeproject.com/Articles/1206764/Clean-Factory-Design-Pattern-with-IoC-Container