Introduction
The Abstract Factory is one of the creational design patterns. It provides an interface for creating families of related objects without specifying their concrete classes.
This pattern is especially useful when the code needs to work with different families of objects and cannot depend on the their concrete classes. It provides a lot of flexibility and adaptability in project.
Problem
I would like to present a problem correlated with my work experience in the industrial sector.
Imagine that we have 2 robots from different manufacturers, such as Yaskawa and ABB. Every manufacturer offers distinct programming methods for their robots. Our task is to connect with their controllers and create “handling operation” for each. What is more, our goal is to establish a structure that allows easy code expansion in case we need to program robot from additional manufacturer in the future.
Structure
IRobotFactory: Abstract factory interface. It declares methods which are used in concrete factories. Methods have to return abstract types of defined products.
YaskawaFactory, AbbFactory: Concrete factories. They implement methods from the abstract factory interface. Each family of product must have own factory. In concrete factories, methods returns products of the concrete type.
IController, IArm: Product interfaces. They declares a set of methods required for concrete products.
YaskawaController, YaskawaArm etc.: Concrete products. They implement methods from the product interfaces. What is important, they represent specific implementations of the product interfaces within their product families.
Application: Client class. It contains some logic, which is based on the abstract of the factory and products. The code does not depend on the concrete type of the product.
Implementation
// C# code
// Abstract Factory interface
internal interface IRobotFactory
{
public IController CreateController();
public IArm CreateArm();
}
// Concrete factories
internal class YaskawaFactory : IRobotFactory
{
public IController CreateController()
{
return new YaskawaController();
}
public IArm CreateArm()
{
return new YaskawaArm();
}
}
internal class AbbFactory : IRobotFactory
{
public IController CreateController()
{
return new AbbController();
}
public IArm CreateArm()
{
return new AbbArm();
}
}
// Product interfaces
internal interface IController
{
public void Connect();
}
internal interface IArm
{
public void Grip();
public void Release();
}
// Concrete products
internal class YaskawaController : IController
{
public void Connect()
{
Console.WriteLine("Connect to Yaskawa robot");
}
}
internal class YaskawaArm : IArm
{
public void Grip()
{
Console.WriteLine("Yaskawa - grip operation");
}
public void Release()
{
Console.WriteLine("Yaskawa - release operation");
}
}
internal class AbbController : IController
{
public void Connect()
{
Console.WriteLine("Connect to ABB robot");
}
}
internal class AbbArm : IArm
{
public void Grip()
{
Console.WriteLine("ABB - grip operation");
}
public void Release()
{
Console.WriteLine("ABB - release operation");
}
}
// Client class
// Create concrete products based on the type of received factory
// Connect with controller
// Grip workpiece
// Release workpiece
internal class Application
{
private IRobotFactory _factory;
public Application(IRobotFactory factory)
{
_factory = factory;
}
public void ExecuteTask()
{
var controller = _factory.CreateController();
var arm = _factory.CreateArm();
controller.Connect();
arm.Grip();
arm.Release();
}
}
internal class Program
{
static void Main(string[] args)
{
// Create client and pass concrete factory
var application = new Application(new YaskawaFactory());
// Call method from concrete factory
application.ExecuteTask();
}
} Result:
Connect to Yaskawa robot
Yaskawa - grip operation
Yaskawa - release operation Conclusion
I presented simple example illustrating the usage of the Abstract Factory pattern. The client class utilizes the factory type received from an external source. It remains unaware of whether the client is utilizing a Yaskawa or Abb factory. This lack of awareness ensures that the client class remains unchanged, even in scenarios where we might introduce a new family of the products (such as Kuka) in the future. This behavior enhances the project structure, making it more flexible and scalable. What is more, Abstract Factory pattern encapsulates the creation of the concrete products within specific factories. This separation isolates client code logic from the creation process, enhancing modularity.
Abstract Factory pattern aligns with:
- Singe Responsibility Principle: Each concrete factory is responsible for creating a specific families of related products.
- Open/Closed Principle: New family of products can be added without requiring modifications to the existing client code.
