Design Pattern
.NET .NET Core C# Patterns Visual Studio

Using Design Patterns in .NET Core – Part 9 – Abstract Factory

Welcome to today’s post.

In today’s post I will be discussing the Abstract Factory design pattern. In a previous post I discussed the Factory design pattern and how to implement it within a .NET Core application. In this post, I will be showing how we can use the Factory design pattern within the implementation of an Abstract Factory within applications.

Like the Factory design pattern, the Abstract Factory design pattern is also a creational design pattern, which creates instances of classes when required.

As I will explain in the next section, the abstract factory pattern is used to conceal the creation of classes within a Factory.

Applicable uses for the Abstract Factory Pattern

For what purpose would we want to use the Abstract Factory design pattern?

If we are already using the Factory design pattern, then we would want to further refactor our code to hide away the implementation details of the creation of our classes within the Factory class. This would make more sense if there were classes we created in our factory and we wanted to decouple the classes being created away from the client class.

Adding an additional layer of abstraction to generate the classes would achieve this. The abstract factory would be an interface that would be visible to the client, who would use the abstract factory to generate instances of the requested classes. The actual classes would be hidden from the client. The diagram for an Abstract Factory design pattern is shown below:

In the next section, I will show how we go from abstract factory interfaces to concrete factory classes.

Implementation of Abstract Factory Classes

In the previous section, as part of an example abstract factory pattern, we had an interface that served as the starting point for our abstract factory pattern. I order to make use of the abstract interface we will need to extend it to an abstract class.

The definition of the Abstract Factory Interface is shown below:

namespace BookLoan.Catalog.API.Factory
{
    public interface IAbstractMediaFactory
    {
        public IMedia CreateBookMedia();
        public IMedia CreateDVDMedia();
        public IMedia CreateCDMedia();
        public IMedia CreateMagazineMedia();
    }
}

Extending the Abstract Factory interface into a concrete class creates methods that return instances of different media classes that share a common interface.

The implementation of the abstract factory class is shown below:

namespace BookLoan.Catalog.API.Factory
{
    public class AbstractMediaFactory: IAbstractMediaFactory
    {
        public IMedia CreateBookMedia()
        {
            return new BookMedia();
        }

        public IMedia CreateCDMedia()
        {
            return new CDMedia();
        }

        public IMedia CreateDVDMedia()
        {
            return new DVDMedia();
        }

        public IMedia CreateMagazineMedia()
        {
            return new MagazineMedia();
        }
    }
}

The Abstract Factory class serves the purpose of hiding the details of implementation of the creation of multiple types of classes. I will show how the Abstract Factory class is used within a Concrete Factory class to serve instances of classes to a client through just one method!

Implementation of Concrete Factory Classes

The Concrete Factory is amended to use the Abstract Media Factory to obtain instances of the media type classes as shown:

using BookLoan.Exceptions;

namespace BookLoan.Catalog.API.Factory
{
    public class ConcreteMediaFactory: IMediaFactory
    {
        private readonly IAbstractMediaFactory _abstractMediaFactory;

        public ConcreteMediaFactory(IAbstractMediaFactory abstractMediaFactory)
        {
            _abstractMediaFactory = abstractMediaFactory;
        }

        public IMedia GetMedia(string media)
        {
            switch (media)
            {
                case "Book":               
                    return _abstractMediaFactory.CreateBookMedia();
                case "DVD":
                    return _abstractMediaFactory.CreateDVDMedia();
                case "CD":
                    return _abstractMediaFactory.CreateCDMedia();
                case "Magazine":
                    return _abstractMediaFactory.CreateMagazineMedia();
                default:
                    throw new GeneralException($"Media type {media} cannot be generated.");
            }
        }
    }
}

The factory class is no longer dependent on the actual concrete classes. The dependency has been abstracted into the abstract factory interface IAbstractMediaFactory. The client that uses the factory is no longer directly dependent on the concrete classes.

The instantiation is now controlled by a call to the abstract media factory interface. Below is the call to instantiate the BookMedia class as shown:

return _abstractMediaFactory.CreateBookMedia();

To initialize the abstract factory in our application classes we use dependency injection and make the abstract factory a singleton class that can be used throughout the application to instantiate instances of the media types. This is shown below from the ConfigureServices() method:

services.AddSingleton<IAbstractMediaFactory, AbstractMediaFactory>();

In the next section I will show how we can improve the specification pattern by applying the abstract factory pattern.

Applying the Abstract Factory Pattern to Improve the Specification Pattern

In a previous post on the specification design pattern I used the factory pattern to create instances of specification classes.  We could apply the abstract factory pattern to create a refactored equivalent of the specification pattern.

An Abstract Specification Factory interface is shown below:

using BookLoan.Models;
using BookLoan.Catalog.API.Services;

namespace BookLoan.Catalog.API.Specification
{
    public interface IAbstractSpecificationFactory
    {
        public ISpecification<BookViewModel> CanSaveBook(IUserContextService userContext);
        public ISpecification<BookViewModel> SetSaveBook(BookViewModel request, 
            IUserContextService userContext);
        public ISpecification<BookViewModel> CanUpdateBook(IUserContextService userContext);
        public ISpecification<BookViewModel> SetUpdateBook(BookViewModel request, 
            IUserContextService userContext);
    }
}

An Abstract Specification Factory class is shown below:  

using BookLoan.Models;
using BookLoan.Catalog.API.Services;

namespace BookLoan.Catalog.API.Specification
{
    public class AbstractSpecificationFactory: IAbstractSpecificationFactory
    {
        public ISpecification<BookViewModel> CanSaveBook(IUserContextService userContext)
        {
            return new CanSaveBook(userContext);
        }
        public ISpecification<BookViewModel> SetSaveBook(BookViewModel request, IUserContextService userContext)
        {
            return new SetSaveBook(request, userContext);
        }
        public ISpecification<BookViewModel> CanUpdateBook(IUserContextService userContext)
        {
            return new CanUpdateBook(userContext);
        }
        public ISpecification<BookViewModel> SetUpdateBook(BookViewModel request, IUserContextService userContext)
        {
            return new SetUpdateBook(request, userContext);
        }
    }
}

The specification classes are instantiated from the IAbstractSpecificationFactory interface from the SpecificationFactory as shown:

using BookLoan.Models;
using BookLoan.Catalog.API.Services;

namespace BookLoan.Catalog.API.Specification
{
    public class SpecificationFactory: ISpecificationFactory
    {
        private readonly IAbstractSpecificationFactory _abstractSpecificationFactory;

        public SpecificationFactory(IAbstractSpecificationFactory abstractSpecificationFactory)
        {
            _abstractSpecificationFactory = abstractSpecificationFactory;
        }

        public ISpecification<BookViewModel> CanSaveBook(IUserContextService userContext)
        {
            return _abstractSpecificationFactory.CanSaveBook(userContext);
        }
        public ISpecification<BookViewModel> SetSaveBook(BookViewModel request, 
            IUserContextService userContext)
        {
            return _abstractSpecificationFactory.SetSaveBook(request, userContext);
        }
        public ISpecification<BookViewModel> CanUpdateBook(IUserContextService userContext)
        {
            return _abstractSpecificationFactory.CanUpdateBook(userContext);
        }
        public ISpecification<BookViewModel> SetUpdateBook(BookViewModel request, 
            IUserContextService userContext)
        {
            return _abstractSpecificationFactory.SetUpdateBook(request, userContext);
        }
    }
}

The instantiation of the AbstractSpecificationFactory is handled by dependency injection as shown:

services.AddSingleton<IAbstractSpecificationFactory, AbstractSpecificationFactory>();

The abstract factory pattern is a design pattern that helps classes achieve SOLD compliance by satisfying the separation of concerns principle by allowing class instantiation outside of the current class. In addition, it helps us keep our code cleaner by abstracting the construction of classes to a dedicated class. This also makes the code unit testable as the class instantiation is moved into separate decoupled classes that can be mocked by a test framework.

That is all for today’s post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial