“Abstract” Factory Design Pattern
In our previous blog post, we looked at how to write factory methods to create objects; however, in this post, we'll examine how an actual abstract factory is implemented, its benefits, and how it can help us customize object creation.
Why do we use this?
In order for the programmer or developer using the object to further extend it and create their own implementations, the abstract factory can be used to return abstract objects, i.e., objects that have only been partially created.
Let's use an illustration to better understand:
public class Coordinates
{
public Coordinates(double x, double y)
{
this.x = x;
this.y = y;
}
public double x { get; set; }
public double y { get; set; }
}
public class CoordinatesFactory
{
public Coordinates Create(double x, double y)
{
return new Coordinates(x, y);
}
}
Here, you can see that we have a simple factory implementation where we are using another class to implement an object and the "Create()" method of that Factory is returning a "Coordinates" object, which can lead to tight coupling.
To solve this, we usually use Abstract implementations of an object using the abstract factory; let's look at an example to see how this works.
Let's say we have a business requirement that requires us to create an employee object with monthly and hourly pay, but the user should be able to specify the difference in salaries when creating objects.
And this is where Abstract Factory comes in handy because we can simply return an abstraction, and later users can simply extend that abstraction to specify salaries in their very own class implementations. Let's see how this works.
namespace Factory.AbstractFactory
{
public interface ISalariedEmployee
{
void Create(string Name);
}
public interface IHourlyPaiedEmployee
{
void Create(string Name);
}
public class SalariedEmployee : ISalariedEmployee
{
public void Create(string Name)
{
Console.WriteLine($"{Name} is working as a full time employee!");
}
}
public class HourlyPaiedEmployee : IHourlyPaiedEmployee
{
public void Create(string Name)
{
Console.WriteLine($"{Name} is working as a part time employee on hourly basis!");
}
}
}
We have now created the necessary interfaces that we can use to create objects. We can now create a Factory for Employee and use these interfaces to return while object creation.
Let's start building our factory:
public interface ISalariedEmployeeFactory
{
ISalariedEmployee Generate();
}
public interface IHourlyPaidEmployeeFactory
{
IHourlyPaidEmployee Generate();
}
public class SalariedEmployeeFactory : ISalariedEmployeeFactory
{
public ISalariedEmployee Generate()
{
Console.WriteLine($"This can be used to create salaried employee!");
return new SalariedEmployee();
}
}
public class HourlyPaidEmployeeFactory : IHourlyPaidEmployeeFactory
{
public IHourlyPaidEmployee Generate()
{
Console.WriteLine($"This can be used to create hourly paid employee!");
return new HourlyPaidEmployee();
}
}
As you can see, the Generate() method of class "HourlyPaidEmployeeFactory" returns the "IHourlyPaidEmployee" interface, allowing us to create N numbers of classes based on the two interfaces that we have. For instance, if we later decided to create an employee who can be salaried but who can also be paid based on the extra hours he worked overtime, we can simply create a new class and utilize existing functionalities to achieve that, and we can also utilize the existing factories to achieve those requirements.
In order to extend existing factories, abstract factories can be very helpful. They can also be used to return abstract objects that can be extended further.
Combined source code for usage of the above abstract factory:
namespace Factory.AbstractFactory
{
public interface ISalariedEmployee
{
void Create(string Name);
}
public interface IHourlyPaidEmployee
{
void Create(string Name);
}
public class SalariedEmployee : ISalariedEmployee
{
public void Create(string Name)
{
Console.WriteLine($"{Name} is working as a full time employee!");
}
}
public class HourlyPaidEmployee : IHourlyPaidEmployee
{
public void Create(string Name)
{
Console.WriteLine($"{Name} is working as a part time employee on hourly basis!");
}
}
public interface ISalariedEmployeeFactory
{
ISalariedEmployee Generate();
}
public interface IHourlyPaidEmployeeFactory
{
IHourlyPaidEmployee Generate();
}
public class SalariedEmployeeFactory : ISalariedEmployeeFactory
{
public ISalariedEmployee Generate()
{
Console.WriteLine($"This can be used to create salaried employee!");
return new SalariedEmployee();
}
}
public class HourlyPaidEmployeeFactory : IHourlyPaidEmployeeFactory
{
public IHourlyPaidEmployee Generate()
{
Console.WriteLine($"This can be used to create hourly paid employee!");
return new HourlyPaidEmployee();
}
}
public class AbstractFactoryMain
{
public static void Main(string[] args)
{
var salariedEmployee = new SalariedEmployeeFactory().Generate();
salariedEmployee.Create("Satish");
Console.WriteLine();
var contractBasisEmployee = new HourlyPaidEmployeeFactory().Generate();
contractBasisEmployee.Create("Dhavan");
}
}
}
That is all the information we have about "Abstract Factory" for now. In subsequent blogs, we will see a lot more patterns that fall into different gamma categories. Follow along and,
Happy Coding…!!!