C# Factory Method Design Pattern

Today I am going to write about a factory method design pattern and how to implement it in C#.

Factory method design pattern is one of the creational design pattern first presented by Gang of Four.

Creational patterns are developed to create the objects for the required classes.

Usage: When we do not know object of which class to be be instantiated then we can use factory method design pattern. Responsibility of the object creation is given to the subclasses.

Before starting to understand the implementation of factory method design pattern, let us try to create the problem which is addressed by factory method design pattern.

To demonstrate this problem, I am taking an example of metro(railway) ticketing requirements.

While traveling through metro trains, one needs to buy a ticket. Ticket can be categorized as SJT(Single Journey Ticket), RJT(Return Journey Ticket), Group Ticket, Period Pass, Trip Pass etc.

Once we choose the ticket to be issued from UI, we have to obtain it’s underlying object so that we can assign that ticket specific values and pass it further.

To achieve this I have created a common interface which shall be inherited by all the ticket types in our system.

public interface IProduct
{
    eProductType ProductType { get; set; }
    decimal Price { get; set; }
    int SourceStationId { get; set; }
    int DestinationStationId { get; set; }
    int Quantity { get; set; }
}

This interface is implemented by 3 classes which are Ticket, PeriodPass and TripPass

public class Ticket : IProduct
{
    public eProductType ProductType { get; set; }
    public decimal Price { get; set; }
    public int SourceStationId { get; set; }
    public int DestinationStationId { get; set; }

    public string TicketId { get; set; }
    public DateTime SaleTime { get; set; }
    public DateTime ValidityTime { get; set; }    
}

public class PeriodPass : IProduct
{
    public eProductType ProductType { get; set; }
    public decimal Price { get; set; }
    public int SourceStationId { get; set; }
    public int DestinationStationId { get; set; }

    public DateTime ValidFrom { get; set; }
    public DateTime ValidTill { get; set; }   
}

public class TripPass : IProduct
{
    public eProductType ProductType { get; set; }
    public decimal Price { get; set; }
    public int SourceStationId { get; set; }
    public int DestinationStationId { get; set; }

    public DateTime ValidFrom { get; set; }
    public DateTime ValidTill { get; set; }
    public int NoOfTrips { get; set; }
}

Now based on the product type(ticket type), we can create an instance of the corresponding class by writing a simple method which will accept enum value of eProductType and will return a type of IProduct.

public IProduct GetProduct(eProductType productType)
{
	switch(eProductType)
	{
		case eProductType.SJT:
			return new Ticket();
		case eProductType.RJT:
			return new Ticket();
		case eProductType.PeriodPass:
			return new PeriodPass();
		case eProductType.TripPass:
			return new TripPass();
	}
}

I have demonstrated the simple factory based on switch condition. But the above approach has some disadvantages. One needs to call the GetProduct() method and needs to pass the product type enum value. When new product gets introduced one needs to modify the switch case. This violates the Single Responsibility principle.

At this juncture, factory method pattern comes to rescue.

For each product which we need to create, we need to have a concrete product factory class. This class will be inherited from an abstract ProductFactory class.

public abstract class ProductFactory
{
    public abstract IProduct GetProduct(eProductType productType);
}

public class TicketFactory : ProductFactory
{
    public override IProduct GetProduct(eProductType productType)
    {
        return new Ticket(productType);
    }
}

public class PeriodPassFactory : ProductFactory
{
    public override IProduct GetProduct(eProductType productType)
    {
        return new PeriodPass(productType);
    }
}

So we have successfully utilized our existing IProduct interace and eProductType enum and created the abstract and concrete factory classes.

Now we need to introduce the creator class which will take the responsibility of identifying the correct product to be created and will return the relevant instance of the IProduct.

public class ProductCreator
{
	private readonly Dictionary<eProductType, ProductFactory> factories;

	private ProductCreator()
	{
		factories = new Dictionary<eProductType, ProductFactory>
		{
			{ eProductType.PeriodPass, new PeriodPassFactory() },
			{ eProductType.TripPass, new TripPassFactory() },			
			{ eProductType.SJT, new TicketFactory() },
			{ eProductType.RJT, new TicketFactory() }			
		};
	}

	public static ProductCreator InitializeProductCreator() => new ProductCreator();

	public IProduct CreateProduct(eProductType productType)
	{
		IProduct product = factories[productType].GetProduct(productType);
		return product;
	}
}

Here I have used dictionary to store the eProductType enum and their associated ProductFactory implementations. Using of dictionary is inspired from this blog post.

Now we are all set to utilize our factory implementation. We just need to have an instance of the ProductCreator class and an enum value of type eProductType.

By calling CreateProduct method on instance of the ProductCreator class will return us an instance of the class which implements the IProduct interface.

What have we achieved by this implementation?
When we want to introduce a new product we need not modify the existing concrete factories. We just need to implement the IProduct interface and need to introduce a new concrete factory class which will return an instance of the new product. Also we should not forget to add the eProductType association with new implementation of the ProductFactory class in the ProductCreator dictionary.

This way we have decoupled the logic of creating object from the creator and delegated this responsibility to the subclasses which are the concrete factory classes.

About sagar

A web and desktop application developer.

Leave a Reply

Your email address will not be published.