Abstract Factory Pattern – Factory of Factories

The Abstract Factory Pattern is a creational design pattern that provides an interface for creating families of related objects without specifying their concrete classes. It belongs to the “Gang of Four” design patterns and promotes flexibility, extensibility, and the principle of dependency inversion.

The pattern addresses the problem of creating multiple related objects that are designed to work together. Rather than directly instantiating concrete classes, the Abstract Factory Pattern introduces an abstract factory interface or class that declares factory methods for creating the objects. Subclasses or implementations of the abstract factory provide the specific logic for creating the objects.

By utilizing the Abstract Factory Pattern, the client code is decoupled from the specific classes of the objects it creates. It works with the abstract factory and product interfaces, enabling the code to work with different families of objects interchangeably. This promotes loose coupling, enhances maintainability, and simplifies the process of switching between different implementations or variants of the objects.

The Abstract Factory Pattern is especially useful in scenarios where there are multiple families of related objects, each tailored for different contexts or configurations. It enables the creation of a cohesive set of objects that are designed to work together, providing a higher level of abstraction and encapsulation.

public enum OS {
    MAC,
    WINDOWS;
}

// Abstract Product A
interface Button {
    void render();
}

// Concrete Product A1
class WindowsButton implements Button {
    @Override
    public void render() {
        System.out.println("Rendering a Windows button.");
    }
}

// Concrete Product A2
class MacOSButton implements Button {
    @Override
    public void render() {
        System.out.println("Rendering a macOS button.");
    }
}

// Abstract Product B
interface Checkbox {
    void render();
}

// Concrete Product B1
class WindowsCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Rendering a Windows checkbox.");
    }
}

// Concrete Product B2
class MacOSCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Rendering a macOS checkbox.");
    }
}

public class MacGUIFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new MacButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new MacCheckbox();
    }
}

// Abstract Factory
interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

// Concrete Factory 1
class WindowsGUIFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

// Concrete Factory 2
class MacOSGUIFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new MacOSButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new MacOSCheckbox();
    }
}

public class AbstractFactory {
    public static GUIFactory getFactory(OS os) {
        switch(os) {
            case MAC:
                return new MacGUIFactory();
            case WINDOWS:
                return new WindowsGUIFactory();
        }
        throw new IllegalArgumentException("Invalid os type.");
    }
}


public class Application {
    public static void main(String[] args) {
        GUIFactory macUIFactory = AbstractFactory.getFactory(OS.MAC);
        macUIFactory.createButton().render();
        macUIFactory.createCheckbox().render();

        GUIFactory windowsUIFactory = AbstractFactory.getFactory(OS.WINDOWS);
        windowsUIFactory.createButton().render();
        windowsUIFactory.createCheckbox().render();
    }
}
Github
abstract factory pattern class diagram
abstract factory pattern class diagram

Known JVM usages

Common Anti-Patterns

While the Abstract Factory Pattern is a powerful design pattern, there are certain anti-patterns or pitfalls that developers should be aware of when implementing it. Here are some common abstract factory anti-patterns:

  1. Monolithic Abstract Factory: Creating a single abstract factory interface or class that encompasses all possible product families and variations can lead to a bloated and unmanageable design. It violates the Single Responsibility Principle (SRP) and results in a monolithic abstract factory that is difficult to understand, maintain, and extend.
  2. Mixing Unrelated Product Families: Including unrelated product families within the same abstract factory can lead to confusion and inappropriate design. The abstract factory should focus on creating a cohesive set of related objects that work together. Mixing unrelated products can result in a complex and convoluted hierarchy, making the codebase difficult to understand and maintain.
  3. Tight Coupling with Concrete Classes: If the concrete factory classes are tightly coupled with specific product implementations, it limits the ability to introduce new product variations or switch between different implementations. The abstract factory pattern should promote loose coupling, allowing for easy substitution and extension of product families.
  4. Violation of Open-Closed Principle (OCP): Failing to design the abstract factory pattern to be open for extension but closed for modification violates the OCP. If adding new product families or variations requires modifying the existing abstract factory or client code, it defeats the purpose of the pattern. The abstract factory should be easily extensible without modifying existing code.
  5. Lack of Dependency Inversion: The abstract factory pattern is built on the principle of dependency inversion, where the client code depends on abstractions (interfaces or abstract classes) rather than concrete implementations. Failing to apply this principle can result in tight coupling between the client code and the concrete factories, making it difficult to switch implementations or introduce new factories.
  6. Overcomplicated Hierarchy: Creating a deep and complex hierarchy of abstract factory interfaces or classes can lead to a confusing and difficult-to-maintain design. The hierarchy should be kept simple and focused on providing the necessary factory methods for creating related objects.
  7. Lack of Documentation and Naming Conventions: Failing to document and provide clear naming conventions for abstract factory interfaces, classes, and methods can make the codebase difficult to understand and navigate. Clear and consistent naming, along with proper documentation, helps developers comprehend the purpose and functionality of the abstract factory pattern.

By avoiding these anti-patterns and adhering to best practices, developers can ensure a more effective and maintainable implementation of the Abstract Factory Pattern.

Factory vs Abstract Factory Pattern

The Factory Pattern and the Abstract Factory Pattern are both creational design patterns that aim to separate object creation from the client code. However, they differ in their purpose and level of abstraction. Here’s a comparison between the two patterns:

Factory Pattern:

  • Purpose: The Factory Pattern focuses on creating individual objects of a single type or class.
  • Structure: It involves a single factory class that encapsulates the object creation logic.
  • Usage: It is suitable when there is a single product type, and the client code needs to create instances of that type.
  • Flexibility: It provides a straightforward way to instantiate objects, but it doesn’t support creating families of related objects or varying object configurations.
  • Example: Creating instances of different types of tickets (ConcertTicket, MovieTicket) using a TicketFactory.

Abstract Factory Pattern:

  • Purpose: The Abstract Factory Pattern focuses on creating families of related objects or multiple product types.
  • Structure: It involves an abstract factory interface or class that declares factory methods for creating objects, and multiple concrete factory implementations for each product family.
  • Usage: It is suitable when there are multiple related product families or variations, and the client code needs to create objects from these families.
  • Flexibility: It provides a higher level of abstraction, allowing easy substitution of entire families of objects and supporting different variations of object configurations.
  • Example: Creating UI components (Button, Checkbox) for different operating systems (Windows, macOS) using an abstract factory (GUIFactory) and concrete factory implementations (WindowsGUIFactory, MacOSGUIFactory).

In summary, the Factory Pattern is suitable for creating individual objects of a single type, while the Abstract Factory Pattern is used to create families of related objects or multiple product types. The Abstract Factory Pattern provides a higher level of abstraction and flexibility, allowing easy switching between different families of objects.