Card image cap

Exploring Design Patterns - Factory Method

Design Patterns  • Posted one month ago

A Factory pattern is one of the twenty three design patterns aimed at solving distinctive problems of software designing. It is categorized as a Creational Pattern - aimed at solving problems related to object instantiations in large applications. The Factory pattern can be stated as:

"just define an interface or abstract class for creating an object but let the subclasses decide which class to instantiate"

which means that the sub classes 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.

Factory Pattern and Loose Coupling:

Its a general tendency to spin up a new instance whenever required using the new keyword. Using a new keyword to create an instance tightly couples a component with the dependency which is instantiated and hence it needs to be avoided as much as possible.

In a few scenarios the choice of the type to be instantiated is resolved at runtime based on a condition. 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 holds responsible for returning an instance of desired type.

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.

Usage Example - C# with .NET Core:

For understanding how Factory works, let's take an example of a Reader application which reads data from different categories of Reader sources, say TierOne, TierTwo, TierThree and so on.

Each Reader source has a single method ReadContent() which returns the list of Readers belonging to each category. We have a controller called ReaderController which takes in a query parameter tierId, basing on which we decide from which tier data shall be read. For starters, the class can look something like this.

public class Reader
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Bio { get; set; }
    public Tier Tier { get; set; }
}

[ApiController]
[Route("api/[controller]")]
public class ReaderController : ControllerBase
{
    [Route("{id}")]
    public IEnumerable<Reader> Read(int id)
    {
        var tierId = (Tier)id;
        if (tierId == Tier.One)
        {
            TierOneReader reader = new TierOneReader();
            return reader.ReadContent();
        }
        else if (tierId == Tier.Two)
        {
            TierTwoReader reader = new TierTwoReader();
            return reader.ReadContent();
        }
        else if (tierId == Tier.Three)
        {
            TierThreeReader reader = new TierThreeReader();
            return reader.ReadContent();
        }
        else if (tierId == Tier.Four)
        {
            TierFourReader reader = new TierFourReader();
            return reader.ReadContent();
        }
        else {
            return new DefaultReader();
        }
    }
}
public class TierTwoReader
{
    public IEnumerable<Reader> ReadContent()
    {
        // some logic specific
        // to TierTwoReader component

        return new List<Reader>().Where(x => x.Tier == Tier.Two);
    }
}

public class TierOneReader 
{
    // something similar
}

public class TierThreeReader 
{
    // something similar
}

public class TierFourReader 
{
    // something similar
}

public class DefaultReader
{
    // something Default
}

where the classes TierOneReader, TierTwoReader, TierThreeReader, TierFourReader each connect to different reader sources and return data through a single method ReadContent(). Now this implementation isn't elegant because the else-if ladder keeps on increasing for each new source added and it also tightly couples the controller logic with the instantiation logic which isn't recommended. We use the factory pattern to refactor this class as below:

Step 1: Extracting Interface and Restructuring

Let's extract an interface out of the TierXReader classes and let the TierXReader implement them.

public enum Tier
{
    One = 1,
    Two = 2,
    Three = 3,
    Four = 4
}

public interface IReader
{
    IEnumerable<Reader> ReadContent();
}

public class TierThreeReader : IReader
{
    public IEnumerable<Reader> ReadContent()
    {
        return new List<Reader>().Where(x => x.Tier == Tier.One);
    }
}

public class TierOneReader : IReader
{
    public IEnumerable<Reader> ReadContent()
    {
            return new List<Reader>().Where(x => x.Tier == Tier.Two);
    }
}

public class TierTwoReader : IReader
{
    public IEnumerable<Reader> ReadContent()
    {
        return new List<Reader>().Where(x => x.Tier == Tier.Three);
    }
}

public class TierFourReader : IReader
{
    public IEnumerable<Reader> ReadContent()
    {
        return new List<Reader>().Where(x => x.Tier == Tier.Four);
    }
}

public class DefaultReader : IReader
{
    public IEnumerable<Reader> ReadContent()
    {
        return new List<Reader>();
    }
}
[ApiController]
[Route("api/[controller]")]
public class ReaderController : ControllerBase
{

    [Route("{id}")]
    public IEnumerable<Reader> Read(int id)
    {
        // declare an abstract type
        IReader reader;
        var tierId = (Tier)id;

        // assign instance to the
        // abstract type and let
        // the component invoke on
        // the abstract type
        if (tierId == Tier.One)
        {
            reader = new TierOneReader();
            return reader.ReadContent();
        }
        else if (tierId == Tier.Two)
        {
            reader = new TierTwoReader();
            return reader.ReadContent();
        }
        else if (tierId == Tier.Three)
        {
            reader = new TierThreeReader();
            return reader.ReadContent();
        }
        else if (tierId == Tier.Four)
        {
            reader = new TierFourReader();
            return reader.ReadContent();
        }
        else {
            return new DefaultReader();
        }
    }
}

Step 2: Separating Instantiation Code from Controller Logic:

Controllers are meant only for view 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 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 DefaultReader();
        }

        return reader;
    }
}

The controller can now simply instantiate the ReaderFactory instance for its usage.

[ApiController]
[Route("api/[controller]")]
public class ReaderController : ControllerBase
{
    [Route("{id}")]
    public IEnumerable<Reader> 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
        return reader.ReadContent();
    }
}

Step 3: Injecting instances rather than instantiating:

Since .NET Core runs comes 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 where ever required.

public interface IReaderFactory
{
    IReader Create(Tier tierId);
}

public class ReaderFactory : IReaderFactory
{
	// Factory.Create() implementation
    // as seen before
}
[ApiController]
[Route("api/[controller]")]
public class ReaderController : ControllerBase
{
    private readonly IReaderFactory factory;

    public ReaderController(IReaderFactory factory)
    {
        this.factory = factory;
    }

    [Route("{id}")]
    public IEnumerable<Reader> Read(int id)
    {
        var tierId = (Tier)id;
        IReader reader = factory.Create(tierId);
        return reader.ReadContent();
    }
}

Other Ways of Creating a Factory:

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:

Approach 1 - A 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;
    }
}

Approach 2 - 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();
    }
}

In the process of refactoring our code into a Factory method, we have made our component comply to the SOLID principles of Single Responsibility, Dependency Inversion, Interface Segregation and Liskov Subsitution. Have you noticed?

factory design pattern in asp.net core asp.net core factory pattern dependency injection asp.net core di factory pattern factory pattern in asp.net core factory pattern in .net core factory pattern c# .net core dotnet factory pattern

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept