$ emrebener
home topics gof design patterns (creational) abstract factory pattern

Abstract Factory Pattern

1. What the abstract factory pattern is

Abstract Factory is a creational pattern for handing out families of related objects through a single interface. The caller asks the factory for the things it needs; the factory hands back a coordinated set: all from the same family, guaranteed to work together.

The contrast with Factory Method is the easiest way to fix it in your head: Factory Method picks one concrete type. Abstract Factory picks a whole family at once. If Factory Method is “which kind of pizza should this store make,” Abstract Factory is “which entire kitchen — pizza, drinks, and silverware — should this store run.”

That family-level guarantee is the only reason to reach for the pattern. It’s also where its weight comes from: a working Abstract Factory in Java is rarely fewer than seven or eight types. When that weight pays for itself is the question §4 takes up.

2. Why families need to stay together

Some objects only make sense as part of a coordinated set. The Abstract Factory pattern exists because that’s a pain you really want to prevent at construction time, not discover at runtime.

The canonical case is cross-platform UI. A Windows app should render with Windows-styled buttons, Windows-styled checkboxes, and Windows-styled menus, never a Windows button sitting next to a Mac checkbox. The mismatch isn’t just cosmetic. Mixing different UI toolkits would actually lead to errors.

Usually, the “family” is a hard correctness boundary rather than a stylistic choice. Abstract Factory puts that boundary in the type system: ask the factory for what you need, and you get back a set guaranteed to belong together.

3. Writing one in Java

The pattern has three layers, and the clearest way to see it is to build them up in order. Carrying on with the cross-platform UI example: we want a Button and a Checkbox that are guaranteed to come from the same toolkit (Windows or Mac), without any caller in the rest of the codebase needing to name WindowsButton or MacCheckbox directly.

3.1. The product interfaces

Start with the things the rest of the application talks to. These are the products — abstract enough that callers don’t care which family they came from:

public interface Button {
    void render();
    void onClick();
}

public interface Checkbox {
    void render();
    void toggle();
}

“Product” is GoF terminology. In the original Gang of Four book, the things any factory creates are called products, and the terminology stuck across pretty much every “factory” pattern (Factory Method too — Pizza was technically the “product” in our Factory Method example, we just didn’t call it that).

The whole rest of the application — every screen, every dialog, every event handler — depends on Button and Checkbox, never on a concrete subclass. That decoupling is the entire point of the exercise; everything else in this section is in service of preserving it.

3.2. The abstract factory interface

Next, define the factory itself: an interface that promises one method per product type, returning the abstract product:

public interface UIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

This is the contract. Anyone who holds a UIFactory can build a button and a checkbox without knowing or caring which family the factory belongs to. Adding a new product type later (say, Menu) means adding Menu createMenu(); to this interface and implementing it in every concrete factory. That’s a real cost the pattern carries, and §4 comes back to it.

3.3. The concrete products and factories

Now the actual implementations, one set per family. The Windows side:

public final class WindowsButton implements Button {
    @Override
    public void render() {
        // draw a Windows-style button
    }

    @Override
    public void onClick() {
        // Windows event hookup
    }
}

public final class WindowsCheckbox implements Checkbox {
    @Override
    public void render() {
        // draw a Windows-style checkbox
    }

    @Override
    public void toggle() {
        // ...
    }
}

public final class WindowsFactory implements UIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }

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

The Mac side has the same shape with different, Mac-specific implementations:

public final class MacButton implements Button {
    // Mac-style render and event hookup
}

public final class MacCheckbox implements Checkbox {
    // Mac-style render and toggle
}

public final class MacFactory implements UIFactory {
    @Override
    public Button createButton() {
        return new MacButton();
    }

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

Nine types in total, broken down by role:

RoleCountTypes
Abstract products2Button, Checkbox
Concrete products4WindowsButton, WindowsCheckbox, MacButton, MacCheckbox
Abstract factory1UIFactory
Concrete factories2WindowsFactory, MacFactory

Windows familyMac familyWindowsFactoryWindowsButtonWindowsCheckboxMacFactoryMacButtonMacCheckboxAbstract interfaces (caller-facing)UIFactoryButtonCheckboxWindows familyMac familyWindowsFactoryWindowsButtonWindowsCheckboxMacFactoryMacButtonMacCheckboxAbstract interfaces (caller-facing)UIFactoryButtonCheckbox

The family boundary is what the diagram makes visible: every concrete type sits inside exactly one column, and the abstract row at the bottom is the only thing the rest of the application ever sees. That’s the unavoidable cost of putting the family guarantee in the type system. There’s no shortcut to the pattern that doesn’t lose the guarantee.

The pattern only earns its keep at the boundary, where exactly one place in the application decides which factory to use:

UIFactory ui = switch (platform()) {
    case WINDOWS -> new WindowsFactory();
    case MAC     -> new MacFactory();
};

After that single decision, every other line of UI code asks ui for buttons and checkboxes, and the family guarantee holds automatically. The concrete product classes are referenced exactly once each, inside the factory that owns them.

4. When not to use it

Abstract Factory is the most expensive pattern in this series. Nine types of scaffolding to swap two implementations is a lot, and the cost compounds as the product set grows. Most of the time you don’t need it. A few smells to watch for:

  • You only have one family, and you’ll only ever have one. If there’s no real “Mac vs Windows” distinction in the foreseeable future, the abstract factory and its lone implementation are unneeded ceremony. Factory Method, or just direct construction, achieves the same thing with a fraction of the code. Without a second family, the abstract factory pattern is never worth it.
  • The “family” isn’t actually a family. When the factory bundles objects together because they happen to be configured together, not because they have to come from the same family, you’ve built a junk drawer with a fancy interface.
  • A DI container would do the same job with less code. This is the big one in modern Java. Spring, Guice, or even hand-rolled constructor injection let you wire concrete implementations once at startup and have the rest of the code hold interfaces, which is structurally what Abstract Factory does, with vastly less boilerplate. If your application already has a DI container, “swap the whole family of UI components” can simply be a configuration change.

The rule of thumb: Abstract Factory earns its place when there are at least two real families, the family boundary is a correctness concern (not just a style choice), and you don’t already have a DI framework that can do the wiring. Hit all three and the pattern’s weight is paying for something. Miss any one and the weight is dead.