Abstract Factory

June 17, 2026 in patterns 5 minutes

A intermediate-level guide to Abstract Factory: before-and-after java code and diagrams for a CS student.

The Theme Matching Nightmare

Imagine you are building a UI library that supports different visual themes. A “Light Mode” might use soft blue buttons and grey scrollbars, while a “Dark Mode” uses charcoal buttons and dark-grey scrollbars. To keep the application looking professional, these components must always match; there is nothing worse than a bright white button sitting in the middle of a pitch-black window.

As you add more components—checkboxes, sliders, text fields—your instantiation logic starts to rot. You find yourself writing repetitive conditional logic every time you need a new piece of the UI. This “Switch Statements” smell occurs when you use if/else or switch blocks to decide which concrete class to instantiate based on a type or a configuration string.

class UIComponent {}
class LightButton extends UIComponent {}
class DarkButton extends UIComponent {}
class LightScrollbar extends UIComponent {}
class DarkScrollbar extends UIComponent {}

class UserInterface {
    private String theme;

    public UserInterface(String theme) {
        this.theme = theme;
    }

    // Problem: Constructor bloat and conditional logic everywhere
    // Risk of mixing LightButton with DarkScrollbar due to manual selection
    public UIComponent createButton() {
        if (theme.equals("LIGHT")) {
            return new LightButton();
        } else if (theme.equals("DARK")) {
            return new DarkButton();
        }
        throw new IllegalArgumentException("Unknown theme");
    }

    public UIComponent createScrollbar() {
        if (theme.equals("LIGHT")) {
            return new LightScrollbar();
        } else if (theme.equals("DARK")) {
            return new DarkScrollbar();
        }
        throw new IllegalArgumentException("Unknown theme");
    }
}

The problem here is two-fold: first, the UserInterface class has grown into a bloated mess of conditional logic. Second, and more dangerously, it creates an opportunity for “mismatched families.” Because each method (createButton, createScrollbar) makes its own independent decision about which theme to use, a small logic error could result in a Light Button being paired with a Dark Scrollbar.

  classDiagram
    direction BT
    UIComponent <|.. LightButton
    UIComponent <|.. DarkButton
    UIComponent <|.. LightScrollbar
    UIComponent <|.. DarkScrollbar
    UserInterface ..> UIComponent : creates
    note for UserInterface "Contains repetitive if/else \nlogic for every component"

Solving the Problem with Abstract Factory

To fix this, we apply the Replace Conditional with Polymorphism refactoring at a structural level. We introduce the Abstract Factory, a creational pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.

Instead of asking “What theme is active?” every time we need a button, we ask a specific factory to handle it. If we have a LightThemeFactory, it knows how to produce the entire suite of light-themed components correctly and consistently.

interface UIComponent {}
class LightButton implements UIComponent {}
class DarkButton implements UIComponent {}
class LightScrollbar implements UIComponent {}
class DarkScrollbar implements UIComponent {}

// The Abstract Factory interface
interface UIFactory {
    UIComponent createButton();
    UIComponent createScrollbar();
}

class LightThemeFactory implements UIFactory {
    public UIComponent createButton() { return new LightButton(); }
    public UIComponent createScrollbar() { return new LightScrollbar(); }
}

class DarkThemeFactory implements UIFactory {
    public UIComponent createButton() { return new DarkButton(); }
    public UIComponent createScrollbar() { return new DarkScrollbar(); }
}

class UserInterface {
    private final UIFactory factory;

    // Client code is decoupled from concrete implementations
    public UserInterface(UIFactory factory) {
        this.factory = factory;
    }

    public UIComponent createButton() { return factory.createButton(); }
    public UIComponent createScrollbar() { return factory.createScrollbar(); }
}

In this refactored version, the client (UserInterface) no longer cares about “Light” or “Dark.” It only knows that it has an object implementing the UIFactory interface. This achieves high cohesion: the logic for what to create is moved out of the UI logic and into dedicated factory objects.

  classDiagram
    direction BT
    UIFactory <|.. LightThemeFactory
    UIFactory <|.. DarkThemeFactory
    UIComponent <|-- LightButton : implements
    UIComponent <|-- DarkButton : implements
    UIComponent <|-- LightScrollbar : implements
    UIComponent <|-- DarkScrollbar : implements
    LightThemeFactory ..> LightButton : creates
    LightThemeFactory ..> LightScrollbar : creates
    DarkThemeFactory ..> DarkButton : creates
    DarkThemeFactory ..> DarkScrollbar : creates
    UserInterface o-- UIFactory : uses

When the application runs, the interaction follows a clear delegation flow. The client asks the factory for a component, and the factory handles the messy details of instantiation.

  sequenceDiagram
    participant UI as UserInterface
    participant F as LightThemeFactory
    participant B as LightButton

    UI ->> F: createButton()
    F ->> B: new LightButton()
    B -->> F: return instance
    F -->> UI: return instance

Abstract Factory vs. Factory Method

It is common to confuse these two, but the distinction lies in the “scale” of creation.

  • Factory Method is about a single product. You have one method that returns one type of object (e.g., just a Button).
  • Abstract Factory is about families of products. It contains multiple factory methods to ensure all objects created belong to the same compatible group (e.g., Buttons, Scrollbars, and Checkboxes that all match).

When to Use It, and When Not To

The Abstract Factory pattern is a powerful tool for maintaining consistency in complex systems, but it comes with architectural costs.

Use it when:

  • Your system needs to be independent of how its products are created, composed, and represented.
  • You have “families” of related objects that must be used together to ensure the system remains consistent.
  • You want to enforce a rule where certain object combinations are invalid (e.g., preventing a Dark Button from appearing in a Light Theme).

Do not use it when:

  • Your product family is small and unlikely to grow. Adding a new component type (like a ColorPicker) requires updating the Abstract Factory interface and every concrete factory implementation, which can lead to “Shotgun Surgery.”
  • You are over-engineering. If your application only ever supports one single theme, adding an entire hierarchy of factories adds unnecessary complexity for no tangible benefit.

Takeaways

  • Use Abstract Factory to ensure that families of related objects are instantiated consistently.
  • It eliminates repetitive conditional logic by delegating instantiation to specialized factory objects.
  • Avoid it if the product family is simple or if you anticipate frequently adding new types of products to the suite.

Usage

Using the Abstract Factory to ensure a consistent family of products

UserInterface ui = new UserInterface(new DarkThemeFactory());
UIComponent btn = ui.createButton(); // Guaranteed to be DarkButton