使用IoC 容器清潔工廠設計模式
目錄
(譯者注: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<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容器中支援的內容。對於簡單的注入器,你可以通過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<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容器負責建立物件並控制它們的生命週期,但處理器工廠內處理器物件的生命週期始終是“單例”,因為它們只從容器注入一次到工廠類。
如果工廠類打算控制處理器的生命週期,那麼從容器注入的物件應該被當作處理器模板。通過這種方法,工廠可以通過克隆處理器模板來建立新物件(從而回收工廠的含義)。
還有其他替代解決方案,即將容器而不是處理器集合傳遞到工廠類中。這樣做將允許容器控制工廠返回的處理器的生命週期。
前面描述的解決方案的另一個方面是加入的新方法,在IProcessor中的IsValidUser和它的實現。這有不同的特性。如果切換邏輯基於單個實體,例如enum或原始型別(如int),則最簡單的方法是將其實現為屬性。使用方法為更復雜的條件檢查提供了靈活性,例如,兩個或更多個引數檢查。因此,方法的方法在某種程度上是更通用的。
也可以不在處理器中使用額外的方法,而是實現其他形式的對映,例如,在上requestType使用屬性,甚至將對映視為對工廠類的附加依賴。如果您有興趣進一步探索,請給我一些評論。
原文地址:https://www.codeproject.com/Articles/1206764/Clean-Factory-Design-Pattern-with-IoC-Container