Click here to Skip to main content
15,881,092 members
Articles / Web Development / ASP.NET / ASP.NET Core
Alternative
Article

Clean Factory Design Pattern with IoC Container

Rate me:
Please Sign up or sign in to vote.
4.57/5 (8 votes)
15 Dec 2017CPOL4 min read 26.9K   167   13   3
Coding principles

Audience

This article expects the audience to have familiarity with Dependency Inversion Principle (DIP) and Factory Design Pattern. For simplicity, the code is not defensive and there are no guarded statements. The code is using Simple Injector, but the described principles apply to other IoC container frameworks as well.

Problem

When implementing a factory class in a project which uses a Inversion of Control (IoC) container, if you arrive at the solution described below, then this article is for you:

C#
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();
			}
		}
	}
}

The example code is a processor factory class implementation, which contain a factory method called Create, and a constructor.

The main problem of the above solution is that the factory class injects its processors's dependencies via its constructor. InternalProcessor has dependencies on IDependencyOne and IDependencyTwo, and ExternalProcessor has dependencies on IDependencyOne and IDependencyThree. As a result, the factory class depends on IDependencyOne, IDependencyTwo and IDependencyThree. Another consequence is that if a new processor is later added, the new processor's dependencies also need to be accomodated in the factory class constructor.

Below is the main program using a Simple Injector 4.0.12 container. The code applies Dependency Inversion Principle via constructor injection, and utilises a container to configure class composition (see my blog for further details).

C#
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;
		}
	}
}

and following is the rest of the code:

C#
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;
		}
	}
} 
C#
namespace DirtyFactory.Dependencies
{
	internal interface IDependencyOne
	{
	}

	internal class DependencyOne : IDependencyOne
	{
	}

	internal interface IDependencyTwo
	{
	}

	internal class DependencyTwo : IDependencyTwo
	{
	}

	internal interface IDependencyThree
	{
	}

	internal class DependencyThree : IDependencyThree
	{
	}
}

In order to simplify the explanation, the solution is described first, followed by a discussion later to explore alternatives solution.

Solution

The solution for the problem above is to push the processors as the factory class dependencies instead.

However, there are a number of changes to make this work end to end.

  1. The factory class needs to be injected with a collection of IProcessor.
  2. The switching logic, previously in the factory class, becomes a collection lookup. As a result, each processor needs to have information about the requestType it serves.
  3. The container needs to reqister all IProcessor in the project.

Consequently, if a new processor is added, the factory class and the rest of the code does not need to change at all, which is ideal.

Below are the changes to the factory class (in bold italic):

C#
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));
		}
	}
}

First, the collection of IProcessor are injected via its constructor in the form of IEnumerable. Actually, the collection interface that can be used depends on what is supported in the IoC container. For Simple Injector, you can pass IList, Array, ICollection, IReadOnlyCollection, or IEnumerable.

Secondly, the switch statement is transformed into a collection lookup inside the Create method. In order to support this, an extra method, called IsValidUser, is added into IProcessor, and the implementation of IProcessor are also changed as a result.

C#
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;
		}
	}
}

Lastly, the container needs to register all processors in the project.

C#
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 provides a number way to do collection registration, e.g., specify the the array or IEnumerable of the concrete implementation, or the assembly. If an assembly is specified, the container performs a reflection to enumerate all concrete implementations in the assembly.

(No other changes are required for the remaining code.)

Discussion

There were questions in stack overflow asking whether IoC container was replacing factory design pattern or not. From what we learn here, the factory pattern still can be used side by side with IoC container. However, the role of factory pattern is changing in the solution described above. The factory is no more responsible for creating objects, but only returning objects which are injected as the factory dependencies (thus reducing the meaning of factory). The IoC container is responsible for creating the objects and controls their life cycle, but the life cycle of the processors objects inside processor factory are always 'singleton', as they are only injected once from the container to the factory class.

If it is intended for the factory class to control the lifecycle of the processors, then the objects injected from the container should be treated as processor templates. With this approach, the factory can create new objects by cloning the processor templates (thus reclaiming the meaning of factory).

There is also other alternative solution, which is passing the the container, instead of a processor collection, into the factory class. Doing this will allow the container to control the life cycle of the processors returned by the factory.

Another aspect of the solution described previously is the addition of the new method, IsValidUser, in the IProcessor and its implementations. There are different flavours to this. If the switching logic is based on a single entity such as enum or primitive types, the easiest way is to implement this as a property. Using a method gives flexibility for more complex conditional checking, e.g., two or more arguments checking. Thus a method approach in a way is a more generic.

It is also possible not using an extra method in the processor and implementing other forms of mapping instead, e.g., using attribute on the requestType, or even treating the mapping as an additional dependency on the factory class. If you are interested in exploring this further, just drop me some comments.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) EMIS
United Kingdom United Kingdom
(You can visit my blog in

Comments and Discussions

 
QuestionIsValidUser Pin
Mauro Sampietro9-Feb-21 3:01
Mauro Sampietro9-Feb-21 3:01 
AnswerRe: IsValidUser Pin
kusnaditjung11-Nov-21 21:26
kusnaditjung11-Nov-21 21:26 
QuestionGreat article - thanks! Pin
Aesop Carl29-Oct-20 10:04
Aesop Carl29-Oct-20 10:04 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.