Understanding Abstract Factory Pattern made Easy

In this detailed article, let's understand what is an Abstract Factory Design Pattern and How to implement it with an illustrating example in C#

Introduction

Factory Method Design Pattern helps in solving problems related to the choice of object type selection and instantiation in large scale applications, where the decision of choosing a specific concrete implementation for a specific type is taken during runtime.

But there can exist scenarios where this single set of concrete components are replicated at several levels, causing multiple related components which have a common theme.

As we go on implementing Factory Methods for such cases, we are left with not one but several factories.
The client should not be aware of all these, but must be provided with an abstraction using which it gets the job done.

We need another layer of abstraction over these sets of factories, which has the choice to pick one factory for a given scenario. This is how we implement an Abstract Factory pattern.

What is an Abstract Factory?

Abstract Factory is a Creational Design Pattern. It is one of the twenty three design patterns.
It solves the problem of choosing a particular implementation over multiple levels of sets of components which share a common theme.

Simply put, an Abstract Factory is a Factory of Factories

How to implement an Abstract Factory?

We create a layer of abstraction over the factories and the client creates a concrete implementation of the abstract factory interface and then uses the object to access the concrete object. In this case the client doesn’t know which concrete object he has received from the factory.

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. Customer uses an instance of a ProductFactory, which can provide a specific concrete implementation for his choice among the above models.

Let’s assume there are three variants of any motorcycle: say Generic, Retro and Limited Edition models.


wp-content/uploads/2022/05/abstract_factory.png

We now have three variants of motorcycle, which come in three calibrations each.

But a customer is not interested in all these details to know the details of a particular model. He just gives a variant and a caliber value to the system to get the details for his requirement.

We use an abstract factory of products which further branches out into three factories for each variant. Each factory results in a concrete implementation of a single specific caliber model.

How to implement Abstract Factory Design Pattern with C# Example

Based on the product example stated above, let’s look at how we can convert the words into classes and implement an abstract factory for the above scenario.

Let’s create an abstract type IVehicle with a single method ShowVehicleInfo() that displays the product information for the variant chosen.

There are three variants of these products based on the engine caliber.

/// <summary>
/// an Abstract Type that represents a Vehicle
/// </summary>
public interface IVehicle
{
    void ShowVehicleInfo();
}

These have their own implementation as follows.

/// <summary>
/// A Litre Class Vehicle
/// </summary>
public interface ILitreVehicle : IVehicle { }

/// <summary>
/// A Concrete Litre Class Vehicle
/// </summary>
public class LitreVehicle : ILitreVehicle
{
    string type;

    public LitreVehicle(string type)
    {
        this.type = type;
    }

    public void ShowVehicleInfo()
    {
        Console.WriteLine("This is a {type} Vehicle of Litre caliber");
    }
}

/// <summary>
/// A Quarter Litre Class Vehicle
/// </summary>
public interface IQuarterLitreVehicle : IVehicle { }

/// <summary>
/// A Concrete Quarter Litre Class Vehicle
/// </summary>
public class QuarterLitreVehicle : IQuarterLitreVehicle
{
    string type;

    public QuarterLitreVehicle(string type)
    {
        this.type = type;
    }

    public void ShowVehicleInfo()
    {
        Console.WriteLine("This is a {type} Vehicle of QuarterLitre caliber");
    }
}

/// <summary>
/// An Economy Class Vehicle
/// </summary>
public interface IEconomyVehicle : IVehicle { }

/// <summary>
/// A Concrete Economy Class Vehicle
/// </summary>
public class EconomyVehicle : IEconomyVehicle
{
    string type;

    public EconomyVehicle(string type)
    {
        this.type = type;
    }

    public void ShowVehicleInfo()
    {
        Console.WriteLine("This is a {type} Vehicle of Economy caliber");
    }
}

We can observe that each of these concrete implementations receive a type parameter through the constructor which conveys the variant of the Product it is: Generic, LimitedEdition or Retro.

/// <summary>
/// Vehicle Factory
/// </summary>
public interface IEditionVehicleFactory
{
    IVehicle CreateVehicle();
}

/// <summary>
/// Retro Vehicle Type Factory
/// </summary>
public interface IRetroEditionVehicleFactory : IEditionVehicleFactory { }

/// <summary>
/// Limited Edition Type Factory
/// </summary>
public interface ILimitedEditionVehicleFactory : IEditionVehicleFactory { }

/// <summary>
/// General Edition Type Factory
/// </summary>
public interface IGeneralEditionVehicleFactory : IEditionVehicleFactory { }

An abstract base type IEditionVehicleFactory forms the base for all the factories representing the variants stated above.

Implementations for each of these factories decide which of the products to return.

/// <summary>
/// A General Factory which returns
/// the instances for each Vehicle
/// </summary>
public class InstanceFactory
{
    /// <summary>
    /// A static Factory which returns an instance for each Vehicle
    /// </summary>
    /// <param name="classType"></param>
    /// <returns></returns>
    public static IVehicle CreateVehicle(string model, string classType)
    {
        switch (classType.ToLowerInvariant())
        {
            case VehicleConstants.QUARTER_LITER_CLASS:
                return new QuarterLitreVehicle(model);
            case VehicleConstants.LITER_CLASS:
                return new LitreVehicle(model);
            case VehicleConstants.ECONOMY_CLASS:
            default:
                return new EconomyVehicle(model);
        }
    }

    /// <summary>
    /// A static Factory which returns an instance for each Factory
    /// </summary>
    /// <param name="model"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    public static IEditionVehicleFactory CreateEdition(string model, string type)
    {
        IEditionVehicleFactory factory;

        switch (model.ToLowerInvariant())
        {
            case VehicleConstants.LIMITED_EDITION:
                factory = new LimitedEditionVehicleFactory(type);
                break;
            case VehicleConstants.RETRO_EDITION:
                factory = new RetroEditionVehicleFactory(type);
                break;
            case VehicleConstants.GENERIC_EDITION:
            default:
                factory = new GeneralEditionVehicleFactory(type);
                break;
        }

        return factory;
    }
}
/// <summary>
/// Concrete implementation of subclass Factory
/// </summary>
public class GeneralEditionVehicleFactory : IGeneralEditionVehicleFactory
{
    string type;

    public GeneralEditionVehicleFactory(string type)
    {
        this.type = type;
    }

    public IVehicle CreateVehicle()
    {
        return InstanceFactory.CreateVehicle("Classic, for the casual riders", type);
    }
}

/// <summary>
/// Concrete implementation of subclass Factory
/// </summary>
public class LimitedEditionVehicleFactory : ILimitedEditionVehicleFactory
{
    string type;

    public LimitedEditionVehicleFactory(string type)
    {
        this.type = type;
    }

    public IVehicle CreateVehicle()
    {
        return InstanceFactory.CreateVehicle("Limited Edition, Limited Stocks", type);
    }
}

/// <summary>
/// Concrete implementation of subclass Factory
/// </summary>
public class RetroEditionVehicleFactory : IRetroEditionVehicleFactory
{
    string type;

    public RetroEditionVehicleFactory(string type)
    {
        this.type = type;
    }

    public IVehicle CreateVehicle()
    {
        return InstanceFactory.CreateVehicle("Retro Edition, For the Vintage Lovers", type);
    }
}

We have three families of factory types along with three product types which depend on them for choice.

We can’t simply ask the client to choose for himself the type needed.

The Client uses a RootVehicleFactory that calls the respective IEditionVehicleFactory as requested.

The IAbstractVehicleFactory is the abstract type that is used by the client for the invocation.

public interface IAbstractVehicleFactory
{
    IEditionVehicleFactory CreateFactory(string model, string type);
}

public class RootVehicleFactory : IAbstractVehicleFactory
{
    public RootVehicleFactory() { }

    public IEditionVehicleFactory CreateFactory(string model, string type)
    {
        return InstanceFactory.CreateEdition(model, type);
    }
}

The IAbstractVehicleFactory implementation RootVehicleFactory forms a base for the families of factories here (Limited / Retro / Generic factories).

On the Client side there’s only one abstract type and a method CreateFactory() to call which returns the desired product.

The choice is made by passing the model and type of product required as parameters to the factory method.

The IAbstractVehicleFactory interface and implementation can then be added as a service into the ASP.NET Core container to be able to inject anywhere.

The Client code can look like below.

/// <summary>
/// Calling Client that calls on the Factory
/// </summary>
public class AbstractFactoryClient
{
    private readonly IAbstractVehicleFactory _factory;

    public AbstractFactoryClient(IAbstractVehicleFactory factory)
    {
        _factory = factory;
    }

    public void ShowVehicleDetails(string model, string type)
    {
        IVehicle product = _factory.CreateFactory(model, type).CreateVehicle();
        product.ShowVehicleInfo();
    }
}

In this way, we can implement an abstract factory pattern at its simplest form.

var rootVehicleFactory = new DesignPatternsExamples.Patterns.Creational.RootVehicleFactory();
var abstractFactoryCallingClient =
    new DesignPatternsExamples.Patterns.Creational.AbstractFactoryClient(rootVehicleFactory);

abstractFactoryCallingClient.ShowVehicleDetails(
    DesignPatternsExamples.Patterns.Creational.VehicleConstants.LIMITED_EDITION,
    DesignPatternsExamples.Patterns.Creational.VehicleConstants.LITER_CLASS
);

As mentioned earlier, one must not try to introduce an abstract factory pattern or any pattern for that sake into the application from the very beginning which might end up in messing up the code; but instead try to adapt only when it is needed.

Advantages and disadvantages of Abstract Factory Design Pattern

  1. The intention of the pattern is simple: to insulate the creation of objects from their usage and create families of related types without having to depend on their concrete implementations.
  2. This results in better management of the types, and since we have an abstract layer of factories over the families of factories; we can easily interchange the concrete implementations without even having to change the code that accesses these objects.
  3. The client can never know what change has happened in the background since all he looks at is a single plain abstract base factory type for invocation.
  4. But on the con side, we end up creating a huge chain of factories and implementations which end up in a complex network of abstract and concrete types.
  5. These are hard to maintain and even harder to implement.

Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

Your email address will not be published. Required fields are marked *