Table of Contents
Introduction
In Object Oriented Programming, we generally create an instance of a type whenever we require using the new keyword.
Using a new keyword to create an instance tightly couples a component with the other component for which the object is being created. Hence it needs to be avoided as much as possible.
Sometimes the choice of the component for which the instance needs to be created needs to be resolved at runtime based on the required conditions. In such cases, where the dependent type doesn’t know which type needs to be created and instead runs on an abstraction of the dependency, we can use a factory class
which is responsible for returning an instance of the desired type.
What is a Factory Method?
Factory Method is one of the twenty three design patterns.
It is a Creational Pattern and aims at solving problems related to object instantiations in large applications.
Factory Method Design Pattern states as follows –
Just define an interface or abstract class for creating an object but let the subclasses decide which class to instantiate.
It states that the subclasses are responsible for the object creation for a class, and they choose which type of object is required to be created.
The subclasses deal with an interface or an abstract type for logic and are designed such that they work with any implementation of that interface or abstract type.
For example, think of a vehicle manufacturer who has three different variants of a motorcycle he has designed.
He has a quarter liter, liter and a commuter caliber model. He needs to develop a functionality where the customer can fetch details for the selected caliber model.
There is also the possibility that these models can expand in the future. When designing such a system, one cannot simply create a new instance of the caliber model type. It couples the implementation detail with the requesting component.
Instead, the Client can use an instance of a ProductFactory, which can provide a specific concrete implementation for his choice among the above models.
In this way, we separate the instantiation logic and new keyword from the type and let a separate type take responsibility for it.
This facilitates for loosely coupled components resulting in an application which is open to change in dependencies.
How to implement Factory Method Design Pattern with C# Example
Let’s take an example of a Reader application which reads data on different categories of Reader sources, say TierOne, TierTwo, TierThree and so on.
Each Reader source has a single method ReadContent() which does something specific to each category.
A ReadingClient class that generally runs some business logic, takes in a query parameter tierId, basing on which we decide from which tier data shall be read.
public enum Tier
{
One,
Two,
Three,
Four,
General
}
public class TierOneReader
{
public void ReadContent()
{
Console.WriteLine("I am a TierOne Reader. I am reading this content");
}
}
public class TierTwoReader
{
public void ReadContent()
{
Console.WriteLine("I am a TierTwo Reader. I am reading this content");
}
}
public class TierThreeReader
{
public void ReadContent()
{
Console.WriteLine("I am a TierThree Reader. I am reading this content");
}
}
public class TierFourReader
{
public void ReadContent()
{
Console.WriteLine("I am a TierFour Reader. I am reading this content");
}
}
public class GenericReader
{
public void ReadContent()
{
Console.WriteLine("I am a Generic Reader. I am reading this content");
}
}
public class ReadingClient
{
public void Read(Tier tierId)
{
if (tierId == Tier.One)
{
TierOneReader reader = new TierOneReader();
reader.ReadContent();
}
else if (tierId == Tier.Two)
{
TierTwoReader reader = new TierTwoReader();
reader.ReadContent();
}
else if (tierId == Tier.Three)
{
TierThreeReader reader = new TierThreeReader();
reader.ReadContent();
}
else if (tierId == Tier.Four)
{
TierFourReader reader = new TierFourReader();
reader.ReadContent();
}
else
{
GenericReader reader = new GenericReader();
reader.ReadContent();
}
}
}
The Tier classes One-Four and a Generic Reader connect to different reader sources and process data through a single method ReadContent().
This implementation works but is not optimistic because the else-if ladder keeps on increasing for each new source added and it also tightly couples the business logic with the instantiation logic which isn’t recommended.
We use the factory pattern to refactor this class as below:
Extract an Interface and Restructure
Let’s extract an interface out of the TierNReader classes and let the TierNReader implement them.
public enum Tier
{
One = 1,
Two = 2,
Three = 3,
Four = 4
}
public interface IReader
{
void ReadContent();
}
public class TierThreeReader : IReader { }
public class TierOneReader : IReader { }
public class TierTwoReader : IReader { }
public class TierFourReader : IReader { }
public class DefaultReader : IReader { }
Separate Instantiation Code from Business Logic
A Business class must only perform business logic.
The Instantiation section is moved to a separate concern, let’s say ReaderFactory.
The class contains a single method Create which returns an instance of type IReader, basing on the tierId passed as method parameter.
public interface IReaderFactory
{
IReader Create(Tier tierId);
}
public class ReaderFactory : IReaderFactory
{
public IReader Create(Tier tierId)
{
IReader reader;
if (tierId == Tier.One)
{
reader = new TierOneReader();
}
else if (tierId == Tier.Two)
{
reader = new TierTwoReader();
}
else if (tierId == Tier.Three)
{
reader = new TierThreeReader();
}
else if (tierId == Tier.Four)
{
reader = new TierFourReader();
}
else
{
reader = new GenericReader();
}
return reader;
}
}
The ReadingClient can now simply instantiate the ReaderFactory instance for its usage.
public class ReadingClient
{
public void Read(int id)
{
var tierId = (Tier)id;
// component depends on
// factory which returns the instance
var factory = new ReaderFactory();
// during runtime based on the condition
IReader reader = factory.Create(tierId);
// invoke on the abstract type
reader.ReadContent();
}
}
Factory Method using Dependency Injection
dotnetcore runs with Dependency Injection and using a new keyword makes components harder to test the logic.
Use Injection to inject an instance of the type ReaderFactory which can then be used wherever required.
public interface IReaderFactory
{
IReader Create(Tier tierId);
}
public class ReaderFactory : IReaderFactory
{
public IReader Create(Tier tierId)
{
IReader reader;
if (tierId == Tier.One)
{
reader = new TierOneReader();
}
else if (tierId == Tier.Two)
{
reader = new TierTwoReader();
}
else if (tierId == Tier.Three)
{
reader = new TierThreeReader();
}
else if (tierId == Tier.Four)
{
reader = new TierFourReader();
}
else
{
reader = new GenericReader();
}
return reader;
}
}
public class ReadingClient
{
private readonly IReaderFactory _factory;
public ReadingClient(IReaderFactory readerFactory)
{
_factory = readerFactory;
}
public void Read(Tier tierId)
{
var reader = _factory.Create(tierId);
reader.ReadContent();
}
}
Different ways of implementing Factory Method
While a class if-else block or switch block gets the job done, eliminating an if-else block for such conditional Type selections can be an interesting case study.
There are several alternatives which make use of Reflections, Dictionaries and even Generic Type parameters. Let’s look at a few interesting approaches.
Implementing Factory Pattern using Dictionary
If we know that a condition decides a Type to return, what else can be a good choice than mapping the instances over a Dictionary and returning matching instances right away.
public class MappingReaderFactory : IReaderFactory
{
private readonly Dictionary<Tier, IReader> map;
public MappingReaderFactory()
{
map = this.Seed();
}
public IReader Create(Tier tierId)
{
if (!map.ContainsKey(tierId))
return new DefaultReader();
return map[tierId];
}
private Dictionary<Tier, IReader> Seed()
{
var map = new Dictionary<Tier, IReader>();
map.Add(Tier.One, new TierOneReader());
map.Add(Tier.Two, new TierTwoReader());
map.Add(Tier.Three, new TierThreeReader());
map.Add(Tier.Four, new TierFourReader());
return map;
}
}
Implementing Factory Pattern using Type constraints
Forget about passing in a condition, what if we can just pass in the Type of the instance we’re interested in and the Factory just returns it right away?
Even that can sound good, even if it means doing away with a conditional attribute.
public interface IFactory
{
T Create<T>() where T : IReader, new();
}
// since the constraint for T is to
// return only a derivative of IReader
// no other Type can be returned
public class Factory : IFactory
{
public T Create<T>() where T : IReader, new()
{
return new T();
}
}
Conclusion
Factory Method design Pattern is one of the 23 Gang of Four Design Patterns and is classified as a Creational Pattern. It describes how to efficiently create and return instances of various implementations of a component during runtime.
Implementing a Factory to separate Instantiation logic from business logic makes components skip using the new keyword and makes them loosely coupled.
On a broader sense, a Factory Method design pattern works similar to a Strategy Behavioral Pattern, where a Strategy is passed onto the Context during runtime to create variations in behavior.
An Abstract Factory Design Pattern takes Factory Method and applies to a Family of Factories, creating a Tree like structural hierarchy.