Skip to content

Abstract Factory (Creational)

Abstract Factory is a creational design pattern that lets you produce families of related objects without specifying their concrete classes.

Imagine you’re creating a furniture shop simulator. Your code consists of classes that represent:

  1. A family of related products, say: Chair + Sofa + CoffeeTable.
  2. Several variants of this family. For example, products Chair + Sofa + CoffeeTable are available in these variants: Modern, Victorian, ArtDeco.

You need a way to create individual furniture objects so that they match other objects of the same family. Customers get quite mad when they receive a Modern sofa with a Victorian chair.

Also, you don’t want to change existing code when adding new products or families of products to the program. Furniture vendors update their catalogs very often, and you wouldn’t want to change the core code each time it happens.

The first thing the Abstract Factory pattern suggests is to explicitly declare interfaces for each distinct product of the product family (e.g., chair, sofa or coffee table). Then you can make all variants of products follow those interfaces. For example, all chair variants can implement the Chair interface; all coffee table variants can implement the CoffeeTable interface, and so on.

The next move is to declare the Abstract Factoryβ€”an interface with a list of creation methods for all products that are part of the product family (for example, createChair, createSofa and createCoffeeTable). These methods must return abstract product types represented by the interfaces we extracted previously: Chair, Sofa, CoffeeTable and so on.

Now, how about the product variants? For each variant of a product family, we create a separate factory class based on the AbstractFactory interface. A factory is a class that returns products of a particular kind. For example, the ModernFurnitureFactory can only create ModernChair, ModernSofa and ModernCoffeeTable objects.

The client code has to work with both factories and products through their respective abstract interfaces. This lets you change the type of a factory that you pass to the client code, as well as the product variant that the client code receives, without breaking the actual client code.

Think of a car manufacturing plant. The Abstract Factory is the general blueprint for a β€œCar Factory.”

A Concrete Factory would be the β€œElectric Car Factory” or the β€œGasoline Car Factory.”

  • The Electric Factory produces β€œElectric Engines” and β€œCharging Ports.”
  • The Gasoline Factory produces β€œInternal Combustion Engines” and β€œFuel Tanks.”

The car assembly line (the client) doesn’t care which factory it’s using; it just knows it needs an β€œEngine” and a β€œFuel System.” As long as the factory provides a compatible set of parts, the car can be built.

classDiagram
    class AbstractFactory {
        <<interface>>
        +createProductA() ProductA
        +createProductB() ProductB
    }
    class ConcreteFactory1 {
        +createProductA() ProductA
        +createProductB() ProductB
    }
    class ConcreteFactory2 {
        +createProductA() ProductA
        +createProductB() ProductB
    }
    class ProductA {
        <<interface>>
    }
    class ProductB {
        <<interface>>
    }
    class ConcreteProductA1
    class ConcreteProductA2
    class ConcreteProductB1
    class ConcreteProductB2

    AbstractFactory <|-- ConcreteFactory1
    AbstractFactory <|-- ConcreteFactory2
    ProductA <|.. ConcreteProductA1
    ProductA <|.. ConcreteProductA2
    ProductB <|.. ConcreteProductB1
    ProductB <|.. ConcreteProductB2
    ConcreteFactory1 ..> ConcreteProductA1 : creates
    ConcreteFactory1 ..> ConcreteProductB1 : creates
    ConcreteFactory2 ..> ConcreteProductA2 : creates
    ConcreteFactory2 ..> ConcreteProductB2 : creates
interface Chair:
method sitOn()
class VictorianChair implements Chair:
method sitOn(): print("Sitting on a Victorian chair.")
class ModernChair implements Chair:
method sitOn(): print("Sitting on a modern chair.")
interface FurnitureFactory:
method createChair(): Chair
method createSofa(): Sofa
class VictorianFurnitureFactory implements FurnitureFactory:
method createChair(): return new VictorianChair()
method createSofa(): return new VictorianSofa()
class ModernFurnitureFactory implements FurnitureFactory:
method createChair(): return new ModernChair()
method createSofa(): return new ModernSofa()
  • Use the Abstract Factory when your code needs to work with various families of related products, but you don’t want it to depend on the concrete classes of those products.
  • Consider implementing the Abstract Factory when you have a class with a set of Factory Methods that blur its primary responsibility.
  1. Map out a matrix of distinct product types versus variants of these products.
  2. Declare abstract product interfaces for all product types.
  3. Declare the Abstract Factory interface with a set of creation methods for all abstract products.
  4. Implement a set of concrete factory classes, one for each product variant.
  5. Extract the factory initialization code to a central place. It should instantiate one of the concrete factory classes, depending on the application configuration or the current environment.

Pros:

  • You can be sure that the products you’re getting from a factory are compatible with each other.
  • You avoid tight coupling between concrete products and client code.
  • Single Responsibility Principle.
  • Open/Closed Principle.

Cons:

  • The code may become more complicated than it should be, since a lot of new interfaces and classes are introduced along with the pattern.
  • Abstract Factory classes are often based on a set of Factory Methods, but you can also use Prototype to compose the methods on these classes.
  • Builder focuses on constructing complex objects step by step. Abstract Factory specializes in creating families of related objects.

// Abstract Products
public interface IButton
{
void Paint();
}
public interface ICheckbox
{
void Render();
}
// Concrete Products
public class WinButton : IButton
{
public void Paint() => Console.WriteLine("Windows Button");
}
public class MacButton : IButton
{
public void Paint() => Console.WriteLine("Mac Button");
}
public class WinCheckbox : ICheckbox
{
public void Render() => Console.WriteLine("Windows Checkbox");
}
public class MacCheckbox : ICheckbox
{
public void Render() => Console.WriteLine("Mac Checkbox");
}
// Abstract Factory
public interface IGUIFactory
{
IButton CreateButton();
ICheckbox CreateCheckbox();
}
// Concrete Factories
public class WinFactory : IGUIFactory
{
public IButton CreateButton() => new WinButton();
public ICheckbox CreateCheckbox() => new WinCheckbox();
}
public class MacFactory : IGUIFactory
{
public IButton CreateButton() => new MacButton();
public ICheckbox CreateCheckbox() => new MacCheckbox();
}
// Client
public class Application
{
private readonly IButton _button;
private readonly ICheckbox _checkbox;
public Application(IGUIFactory factory)
{
_button = factory.CreateButton();
_checkbox = factory.CreateCheckbox();
}
public void Render()
{
_button.Paint();
_checkbox.Render();
}
}
// Usage
class Program
{
static void Main()
{
IGUIFactory factory = new WinFactory();
var app = new Application(factory);
app.Render();
}
}