LLDCreational Design PatternsAbstract Factory

Abstract Factory

Creational Design Patterns

In the last chapter, we used the Factory Method to create a single Transport object. But what if you need to create a whole set of related objects that are designed to work together?

Consider a cross-platform application that needs to support different UI themes, like Windows and macOS. A consistent theme requires that all UI elements match. You need a WindowsButton, a WindowsCheckbox, and a WindowsTextField for the Windows theme. For macOS, you need a MacButton, a MacCheckbox, and a MacTextField.

How can your application create a full UI without scattering if (os == 'windows') logic everywhere? More importantly, how do you guarantee you don't accidentally create a MacCheckbox in a Windows-themed window? The client code needs a way to say, "Give me the complete set of UI elements for the current theme," and be sure they are all from the same family.

A Factory for Factories

The Abstract Factory is a creational pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.

The key word here is families. While the Factory Method deals with creating one type of product, the Abstract Factory deals with creating a suite of different products that are designed to be used together. You can think of it as a "factory for factories," where the Abstract Factory defines the contract for creating a whole set of related items.

The Mechanics: The Participants

This pattern has more moving parts than the Factory Method:

  1. Abstract Products: An interface for each distinct product in the family (e.g., IButton, ICheckbox).
  2. Concrete Products: The concrete implementations for each product, for each family (e.g., WindowsButton, MacButton, WindowsCheckbox, MacCheckbox).
  3. Abstract Factory: A single interface that defines a method for creating each of the abstract products (e.g., IGuiFactory with createButton() and createCheckbox() methods).
  4. Concrete Factories: A class for each product family that implements the AbstractFactory interface. It knows how to create all the products for one specific, consistent theme (e.g., WindowsFactory creates WindowsButton and WindowsCheckbox).

Example: The GUI Factory

Let's implement our cross-platform UI factory.

Step 1: Define the Abstract Products

public interface IButton { void paint(); }
public interface ICheckbox { void paint(); }

Step 2: Create the Concrete Product Families

// Windows family
public class WindowsButton implements IButton { /* ... */ }
public class WindowsCheckbox implements ICheckbox { /* ... */ }

// macOS family
public class MacButton implements IButton { /* ... */ }
public class MacCheckbox implements ICheckbox { /* ... */ }

Step 3: Define the Abstract Factory Interface

This is the contract for any factory that can create a full set of UI elements.

public interface IGuiFactory {
    IButton createButton();
    ICheckbox createCheckbox();
}

Step 4: Create the Concrete Factories

Each factory knows how to create a matched set of products.

public class WindowsFactory implements IGuiFactory {
    @Override public IButton createButton() { return new WindowsButton(); }
    @Override public ICheckbox createCheckbox() { return new WindowsCheckbox(); }
}

public class MacFactory implements IGuiFactory {
    @Override public IButton createButton() { return new MacButton(); }
    @Override public ICheckbox createCheckbox() { return new MacCheckbox(); }
}

Step 5: The Client is Configured with a Factory

The client code is configured with a specific factory at runtime. It is completely decoupled from the concrete UI elements and only works with the abstract interfaces.

public class Application {
    private final IButton button;
    private final ICheckbox checkbox;

    public Application(IGuiFactory factory) {
        // The application receives a factory and uses it to create its UI elements.
        // It doesn't know the concrete types of the elements it's creating.
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void paint() {
        button.paint();
        checkbox.paint();
    }
}

public class ApplicationConfigurator {
    public static void main(String[] args) {
        // Configuration can come from a file, environment variable, etc.
        String osName = System.getProperty("os.name").toLowerCase();
        IGuiFactory factory;

        if (osName.contains("win")) {
            factory = new WindowsFactory();
        } else if (osName.contains("mac")) {
            factory = new MacFactory();
        } else {
            throw new UnsupportedOperationException("Your OS is not supported.");
        }

        Application app = new Application(factory);
        app.paint();
    }
}

Interview Focus: Analysis and Trade-offs

Benefits:

  • Guarantees Consistency: The primary benefit. Since a concrete factory creates a whole family of products, you are guaranteed that the products you get are from the same variant and are compatible with each other.
  • Excellent for SOLID: It's a great example of OCP, as you can add a whole new family (e.g., LinuxFactory) without changing the client code. It also promotes DIP by making the client depend on the abstract factory interface, not concrete factories.
  • Isolates Concrete Classes: The client code is completely decoupled from the concrete product implementations, leading to a highly flexible system.

Drawbacks:

  • High Complexity: This is the main trade-off. The pattern introduces a lot of new interfaces and classes, which can make the codebase more complex if you only have a few product families.
  • Difficult to Add New Products: While it's easy to add new families (like LinuxFactory), it is very difficult to add a new product type (like a ITextField). Doing so would require changing the IGuiFactory interface, which would force you to modify all of your existing concrete factory classes. This is a critical point to raise in an interview.

How Abstract Factory Influences System Design

  • Factory Method vs. Abstract Factory: This is the most common point of confusion and a great topic to clarify in an interview.
    • Factory Method uses inheritance to let subclasses decide which single product to create.
    • Abstract Factory uses composition (the client holds a factory object) to create families of related products.
    • Often, the methods inside an Abstract Factory (createButton(), createCheckbox()) are themselves Factory Methods.
  • Singleton Pattern: The concrete factory classes (WindowsFactory, MacFactory) are often implemented as Singletons. This makes sense because you typically only need one instance of each factory to create all the products for that family.
  • Real-World Use Cases: This pattern shines whenever you need to provide interchangeable "kits" of components. This is common for:
    • UI Theming / Skinning: As in our example.
    • Database Abstraction: Creating a family of Connection, Command, and DataReader objects for different database vendors (e.g., a SqlServerFactory vs. an OracleFactory).
    • Supporting Different Document Formats: Creating parsers and renderers for different file types like PDF, HTML, or DOCX.