65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.57/5 (8投票s)

2017年12月15日

CPOL

4分钟阅读

viewsIcon

28325

downloadIcon

195

编码原则

目标读者

本文期望读者对依赖倒置原则(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 依赖于 IDependencyOneIDependencyTwo,而 ExternalProcessor 依赖于 IDependencyOneIDependencyThree。因此,工厂类依赖于 IDependencyOneIDependencyTwoIDependencyThree。另一个后果是,如果以后添加新的处理器,还需要在工厂类的构造函数中适应新处理器的依赖项。

以下是使用 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
	{
	}
}

为了简化解释,首先描述解决方案,然后讨论探索替代方案。

解决方案

上述问题的解决方案是将处理器推入工厂类的依赖项中。

然而,为了实现端到端工作,需要进行一系列更改。

  1. 工厂类需要注入一个 IProcessor 的集合。
  2. 先前在 factory 类中的切换逻辑,现在变成了集合查找。因此,每个处理器都需要有关它所服务的 requestType 的信息。
  3. 容器需要注册项目中的所有 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,您可以传递 IListArrayICollectionIReadOnlyCollectionIEnumerable

其次,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 上的属性,甚至将映射视为工厂类的附加依赖项。如果您有兴趣进一步探讨这个问题,请随时留下评论。

© . All rights reserved.