Card image cap

Exploring Design Patterns - Using Factory Pattern for Type Selection in ASP.NET Core

Developing an application software is quite simple, but developing an application software which is testable, extensible and decoupled is hard. And developing such applications as they grow in size create many structural and design level problems which might result in less maintainable and less robust softwares. A Design Pattern is a specific design solution developed to solve such a structural problem one at a time during a software development. These are software design recommendations and examples developed by GoF (Gang of Four) which aim at solving frequently occurring design problems in our applications. Using these patterns help us design extensible and elegant softwares, which are further open to extension and change.

While there are several such patterns e.g: Singleton, NullObject, Adapter and so on, in this article let's discuss one such design pattern developed in solving issue related to selective instantiation, called as the Factory Pattern.

What is a Factory Pattern?

A Factory pattern is a Creational Pattern: aimed at solving problems related to object instantiations in large applications. The Factory pattern says that:

"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:

We know that using a new keyword tightly couples a component with its dependency, and hence needs to be avoided as much as possible. In scenarios where the choice of the type to be instantiated is decided during runtime and the dependent type doesn't know which type needs to be created, 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 thereby resulting in an application which is open to change in dependencies..

Usage Example in ASP.NET Core - types of Reader sources and usage in a Controller:

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..

Now 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.

[ApiController]
[Route("api/[controller]")]
public class ReaderController : ControllerBase
{
    [Route("{tierId}")]
    public IEnumerable<Reader> Read(int tierId)
    {
        if (tierId == 1)
        {
            TierOneReader reader = new TierOneReader();
            return reader.ReadContent();
        }
        else if (tierId == 2)
        {
            TierTwoReader reader = new TierTwoReader();
            return reader.ReadContent();
        }
        else if (tierId == 3)
        {
            TierThreeReader reader = new TierThreeReader();
            return reader.ReadContent();
        }
        else if (tierId == 4)
        {
            TierFourReader reader = new TierFourReader();
            return reader.ReadContent();
        }
        else {
            return new List<Reader>();
        }
    }
}


internal class TierTwoReader
{
    internal IEnumerable<Reader> ReadContent()
    {
        return new List<Reader>();
    }
}

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 interface IReader
{
    IEnumerable<Reader> ReadContent();
}

public class TierThreeReader : IReader
{
    public IEnumerable<Reader> ReadContent()
    {
        return new List<Reader>();
    }
}

[ApiController]
[Route("api/[controller]")]
public class ReaderController : ControllerBase
{
    [Route("{tierId}")]
    public IEnumerable<Reader> Read(int tierId)
    {
        IReader reader;

        if (tierId == 1)
        {
            reader = new TierOneReader();
            return reader.ReadContent();
        }
        else if (tierId == 2)
        {
            reader = new TierTwoReader();
            return reader.ReadContent();
        }
        else if (tierId == 3)
        {
            reader = new TierThreeReader();
            return reader.ReadContent();
        }
        else if (tierId == 4)
        {
            reader = new TierFourReader();
            return reader.ReadContent();
        }
        else {
            return new List<Reader>();
        }
    }
}

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
{
    public IReader Create(int tierId)
    {
        IReader reader;

        if (tierId == 1)
        {
            reader = new TierOneReader();
        }
        else if (tierId == 2)
        {
            reader = new TierTwoReader();
        }
        else if (tierId == 3)
        {
            reader = new TierThreeReader();
        }
        else if (tierId == 4)
        {
            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("{tierId}")]
    public IEnumerable<Reader> Read(int tierId)
    {
        var factory = new ReaderFactory();
        var reader = factory.Create(tierId);
        return reader.ReadContent();
    }
}

Step 3: Injecting instances rather than instantiating:

Since ASP.NET Core contains a DI container, and using a new keyword makes it harder to test the logic, let's use the DI to inject an instance of the type ReaderFactory which can then be used at our disposal.

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

public class ReaderFactory : IReaderFactory
{
public IReader Create(int tierId)
    {
   // Factory logic here
}
}

[ApiController]
[Route("api/[controller]")]
public class ReaderController : ControllerBase
{
    private IReaderFactory factory;

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

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

In this way, we can implement factory pattern in ASP.NET Core and in the process of refactoring the code for the same, we have applied the SOLID principles of Single Responsibility, Dependency Inversion, Interface Segregation and Liskov Subsitution. Interesting isn't it?

Other Creational Patterns:

The Abstract Factory Pattern

The Factory Pattern (this)

The Prototype Pattern

The Singleton Pattern

Published 3 months ago

Sponsored Links
We use cookies to improve user experience. Learn More