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

Using Design Patterns in .NET Core – Part 2 – Factory

Welcome to today’s post.

In today’s post I will be discussing the next software design pattern which is the Factory design pattern. A Factory design pattern is a creational pattern, which deals with the generation of classes. What this means is that factory creates a different concrete instance of a class depending on parameters passed into the factory constructor.

Unlike a behavioral pattern which affects the business logic or functionality of the class, or a structural pattern, which affects the shape of a class in terms of properties, members, and methods, a creational pattern only purpose is to generate different classes that are derived from a base class.

Scenarios where we apply Factory Patterns

In the context of a .NET Core Web API application, we can generate the classes in the application start up then inject these classes in our API services.

Let us look at a scenario which we can use to utilise the factory pattern.

In our simple library system, we have books that we loan out to borrowers. What if we wanted to expand our library operations to include CDs, DVDs, and Magazines?

Instead of generating an instance of each type of library media class manually, we will use a factory to generate these.

For each of the media types has a member function CanBeLoaned()  that tells us if the media type can be loaned for the specified number of weeks loaned. All media types are defined from a factory interface contract with this common member function.

The factory interface UML diagram is shown below:

In the next section, I will show how to apply the factory pattern in a .NET Core application.

Interfaces within the Factory Pattern

An Interface is essentially a contract that defines a set of members that a class must implement. Where different classes implement from the same interface, they vary by the implementation within the respective classes, which shapes the differences in behavior between the different class implementations.

From our diagram, to start building our factory, we declare a base media interface for each of our media types:

namespace BookLoan.Catalog.API.Factory
{
    public interface IMedia
    {
        bool CanBeLoaned(int weeks);
    }
}

The media factory interface is a contract that has a function that obtains an instance of a media type given a specific media name. The media factory is defined below:

namespace BookLoan.Catalog.API.Factory
{
    public interface IMediaFactory
    {
        public IMedia GetMedia(string media);
    }
}

The concrete implementation of each media type extends the factory with an implementation of the CanBeLoaned(). Below is the BookMedia class that extends the IMedia interface:

namespace BookLoan.Catalog.API.Factory
{
    public class BookMedia: IMedia
    {
        public bool CanBeLoaned(int weeks)
        {
            return (weeks >= 1) && (weeks <= 4);
        }
    }
}

Below is the CDMedia class that extends the IMedia interface:

namespace BookLoan.Catalog.API.Factory
{
    public class CDMedia: IMedia
    {
        public bool CanBeLoaned(int weeks)
        {
            return (weeks >= 1) && (weeks <= 3);
        }
    }
}

Below is the DVDMedia class that extends the IMedia interface:

namespace BookLoan.Catalog.API.Factory
{
    public class DVDMedia: IMedia
    {
        public bool CanBeLoaned(int weeks)
        {
            return (weeks >= 1) && (weeks <= 3);
        }
    }
}

Below is the MagazineMedia class that extends the IMedia interface:

namespace BookLoan.Catalog.API.Factory
{
    public class MagazineMedia: IMedia
    {
        public bool CanBeLoaned(int weeks)
        {
            return (weeks >= 1) && (weeks <= 2);
        }
    }
}

I will now show how we generate instances of the above classes in the next section.

Generating Instances within the Factory Pattern

The most important part of the factory pattern is the ability to generate instances of classes without using the new operator, which is hidden within the factory class.

We implement IMediaFactory as a concrete class ConcreteMediaFactory, which generates instances of media types from the media type name.

The factory implementation is shown below:

using BookLoan.Exceptions;


namespace BookLoan.Catalog.API.Factory
{
    public class ConcreteMediaFactory: IMediaFactory
    {
        public IMedia GetMedia(string media)
        {
            switch (media)
            {
                case "Book":
                    return new BookMedia();
                case "DVD":
                    return new DVDMedia();
                case "CD":
                    return new CDMedia();
                case "Magazine":
                    return new MagazineMedia();
                default:
                    throw new GeneralException($"Media type {media} cannot be generated.");
            }
        }
    }
}

What we did was to pass the media type into the factory method, which then returned an instance one of the above four media classes. From the instance we then called the CanBeLoaned() method and returned its value.

In our application, the media type passed into the factory is obtained from our data source for each Book within our service class.

We could also have instantiated each of our IMedia interfaces using an implementation factory from a ServiceCollection extension method within the application start up method StartupServices(). This approach would be suitable only for constructor parameters from the configuration settings.

In our application startup we setup the factory interface injection as follows:

using BookLoan.Catalog.API.Factory;
…
public void ConfigureServices(IServiceCollection services)
{
    …
    services.AddTransient<IMediaFactory, ConcreteMediaFactory>();
    …

We can then inject the factory into our service class using the standard method through the constructor:

using BookLoan.Catalog.API.Factory;

public class BookService: IBookService
{
    …
    private readonly IMediaFactory _mediaFactory;

    public BookService(…
            IMediaFactory mediaFactory, …)
    {
        …
        _mediaFactory = mediaFactory;
    }

    public async Task<bool> MediaHas4WeekLoan(int id)
    {
        BookLoan.Models.BookViewModel book = 
            await _bookRepository.GetByIdAsync(id);
        return _mediaFactory
            .GetMedia(book.MediaType)
            .CanBeLoaned(4);
    }
    ....

What we did was to pass the media type into the factory method, which then returned an instance one of the above four media classes. From the instance we then called the CanBeLoaned() method and returned its value.

What we have seen is an example of how to use the factory pattern in .NET Core. The use of the pattern itself has allowed us to clean up our code instead of using multiple conditional or switch statements, encapsulate business logic, and make our code more testable.

Of all the patterns we can use, the factory pattern provides what we need to satisfy the SOLID principles even though it breaks the purpose of using dependency injection to decouple all interfaces from concrete classes. Clearly encapsulating object creation into a separate factory class satisfies the separation of concerns, allowing additional types to be generated by our factory satisfies the open-closed extensibility principle, interfaces are clearly segregated, and dependency injection is used to instantiate the factory.

Where we utilise manual means to instantiate our classes using implementation factories requires additional work to dispose of them outside of the container framework that .NET Core provides.

I have showed one way in which to utilize the factory method pattern, and there are more variations on how to implement it within a .NET Core application.

That is all for this post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial