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:
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).
using System;
using DirtyFactory.Dependencies;
using DirtyFactory.Processors;
using SimpleInjector;
namespace DirtyFactory
{
internal class Program
{
internal static IProcessorFactory _processorFactory;
static void Main(string[] args)
{
Container container = GetRegisteredContainer();
_processorFactory = container.GetInstance<IProcessorFactory>();
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:
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
{
}
}
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.
- The factory class needs to be injected with a collection of
IProcessor
. - 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. - 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):
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.
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.
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)
{
Container container = GetRegisteredContainer();
_processorFactory = container.GetInstance<IProcessorFactory>();
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>();
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.