SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignAbstract Factory Pattern
Software DesignCreational Patterns

Abstract Factory Pattern

Create families of related objects without specifying their concrete classes — swap entire product families at once.

January 15, 20255 min read
design-patternsjavaabstract-factorycreational

One-line Definition

Encapsulates the creation of related object families so clients depend on abstractions instead of concrete groups.


Problem

Suppose your application generates UI components for both Windows and MacOS. A Windows button and a Mac checkbox don't mix well visually or functionally. If client code instantiates these directly, it's easy to accidentally mix families or scatter OS-specific if checks everywhere. Abstract Factory solves this by forcing the creation of a consistent set of products.


Real-world Analogy

Think of a furniture store that sells matching sets: a "Modern" collection and a "Victorian" collection. When you pick "Modern," you get a modern chair, modern table, and modern sofa — all guaranteed to match. You never mix a Victorian chair with a modern table. The abstract factory is the catalog that ensures you always get a consistent set.


Structure

  • Client
    • Requests objects from the factory and works only with abstract interfaces, never concrete families.
  • AbstractFactory
    • Interface declaring creation methods for each product in the family.
  • ConcreteFactory
    • Implements the AbstractFactory to produce a specific family of products.
  • AbstractProduct
    • Interface for each kind of product.
  • ConcreteProduct
    • The actual product implementations.

Diagram

classDiagram
    class Client {
    }
    class GUIFactory {
        <<interface>>
        +createButton(): Button
        +createCheckbox(): Checkbox
    }
    class WindowsFactory {
        +createButton(): Button
        +createCheckbox(): Checkbox
    }
    class MacFactory {
        +createButton(): Button
        +createCheckbox(): Checkbox
    }
    
    Client --> GUIFactory
    WindowsFactory ..|> GUIFactory
    MacFactory ..|> GUIFactory

Code Walkthrough

Notice that the Application (client) depends only on GUIFactory, Button, and Checkbox. The concrete factory ensures the app always gets a matching family of UI components.

interface Button {
    void render();
}

interface Checkbox {
    void render();
}

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

class MacButton implements Button {
    @Override
    public void render() {
        System.out.println("Rendering Mac button");
    }
}

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

class MacCheckbox implements Checkbox {
    @Override
    public void render() {
        System.out.println("Rendering Mac checkbox");
    }
}

interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

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

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

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

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

class Application {
    private final Button button;
    private final Checkbox checkbox;

    public Application(GUIFactory factory) {
        this.button = factory.createButton();
        this.checkbox = factory.createCheckbox();
    }

    public void renderUI() {
        button.render();
        checkbox.render();
    }
}

class Main {
    public static void main(String[] args) {
        String os = System.getProperty("os.name").toLowerCase();
        GUIFactory factory;

        if (os.contains("win")) {
            factory = new WindowsFactory();
        } else {
            factory = new MacFactory();
        }

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

Bad vs Good

Bad Approach

Problems

  • Client code is littered with OS checks for every component.
  • Easy to accidentally mix a Windows button with a Mac checkbox.
class Application {
    public void renderUI(String os) {
        if (os.equals("windows")) {
            System.out.println("Rendering Windows button");
            System.out.println("Rendering Windows checkbox");
        } else if (os.equals("mac")) {
            System.out.println("Rendering Mac button");
            System.out.println("Rendering Mac checkbox");
        }
    }
}

class Main {
    public static void main(String[] args) {
        Application app = new Application();
        app.renderUI("windows");
    }
}

Better Approach

Improvements

  • Each factory guarantees a consistent product family.
  • New platforms are added by extending the factory, keeping business logic unchanged.
interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

class WindowsFactory implements GUIFactory {
    @Override
    public Button createButton() { return new WindowsButton(); }
    @Override
    public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}

class MacFactory implements GUIFactory {
    @Override
    public Button createButton() { return new MacButton(); }
    @Override
    public Checkbox createCheckbox() { return new MacCheckbox(); }
}

class Application {
    private final Button button;
    private final Checkbox checkbox;

    public Application(GUIFactory factory) {
        this.button = factory.createButton();
        this.checkbox = factory.createCheckbox();
    }

    public void renderUI() {
        button.render();
        checkbox.render();
    }
}

Pros vs Cons

ProsCons
Guarantees product family consistencyHigh class count — every product needs an interface and multiple implementations
Decouples client code from concrete typesAdding a new product type (e.g., Slider) requires updating every factory
Swapping product families is a one-line changeOverkill for single product families
Centralizes family creation logicIncreases upfront design complexity
Prevents accidental mixing of incompatible objectsHard to navigate for new team members

When to Use

  • Your system needs to work with multiple families of products.
  • Products in a family must be used together to function correctly.
  • You want to enforce consistency across a group of instantiated objects.
  • You're building a cross-platform application or theme system.

When Not to Use

  • You only have one product family.
  • Products do not need to be rigorously grouped.
  • The product families change frequently, requiring constant updates to all factories.
  • A simpler Factory Method suffices.

Real-world Examples

  • javax.xml.parsers.DocumentBuilderFactory
  • javax.xml.transform.TransformerFactory
  • javax.xml.xpath.XPathFactory
  • GUI toolkits (creating buttons, text fields, etc. matching a specific LookAndFeel)

Key Takeaway

The Abstract Factory Pattern ensures that related objects are always created together as a consistent family, making it impossible to mix incompatible components. Use it when your system supports multiple "themes" or "platforms," but avoid it if you only have a single product family, as a simpler Factory Method is sufficient.

PreviousPrototype PatternNextObserver Pattern