Creational

Abstract Factory

Provide an interface for creating families of related or dependent objects without specifying concrete classes.

Intermediate Pattern #03 of 23

What is the Abstract Factory Pattern?

Abstract Factory provides an interface for creating families of related objects. Where Factory Method creates one product, Abstract Factory creates an entire suite — a "kit" of related products that are designed to work together.

The key constraint is that all products created by one factory are compatible with each other, but incompatible with products from a different factory.

Real-world analogy: IKEA furniture collections — if you buy from the "MALM" collection, every piece (bed, dresser, nightstand) matches. Mixing with "HEMNES" breaks the aesthetic. The collection is your Abstract Factory; each piece is a product family.

The Problem It Solves

Consider a cross-platform UI toolkit. You need Buttons, Checkboxes, and TextFields — but each must be platform-specific (Windows or Mac). If you create them independently, you risk mixing Windows buttons with Mac checkboxes, which breaks visual consistency.

You need to guarantee that all UI components created for Windows come from one family, and all Mac components from another — with no cross-contamination.

Participants

AbstractFactory
Interface declaring creation methods for each distinct product in the family (e.g., createButton(), createCheckbox()).
ConcreteFactory
Implements all creation methods to produce a specific family of products (e.g., WindowsFactory, MacFactory).
AbstractProduct
Interface for each type of product (Button, Checkbox). All concrete products of that type implement this.
ConcreteProduct
Specific implementation of a product (WindowsButton, MacButton). Products within a family are designed to work together.
Client
Uses only AbstractFactory and AbstractProduct interfaces — never knows about concrete classes.

Visual Flow Diagram

«interface» GUIFactory createButton() / createCheckbox() WindowsFactory createButton() createCheckbox() MacFactory createButton() createCheckbox() WindowsButton WinCheckbox MacButton MacCheckbox Client uses factory

Java Code Example

Java
// Abstract Product interfaces
public interface Button { void paint(); }
public interface Checkbox { void paint(); }

// Windows family
public class WindowsButton implements Button {
    public void paint() { System.out.println("Windows Button"); }
}
public class WindowsCheckbox implements Checkbox {
    public void paint() { System.out.println("Windows Checkbox"); }
}

// Mac family
public class MacButton implements Button {
    public void paint() { System.out.println("Mac Button"); }
}
public class MacCheckbox implements Checkbox {
    public void paint() { System.out.println("Mac Checkbox"); }
}

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

// Concrete Factories
public class WindowsFactory implements GUIFactory {
    public Button   createButton()   { return new WindowsButton(); }
    public Checkbox createCheckbox() { return new WindowsCheckbox(); }
}
public class MacFactory implements GUIFactory {
    public Button   createButton()   { return new MacButton(); }
    public Checkbox createCheckbox() { return new MacCheckbox(); }
}

// Client — depends only on interfaces
public class Application {
    private Button button;
    private Checkbox checkbox;

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

// Bootstrap — only place that knows concrete factory
public class Main {
    public static void main(String[] args) {
        GUIFactory factory = getFactory(System.getProperty("os.name"));
        new Application(factory).paint();
    }
    static GUIFactory getFactory(String os) {
        return os.contains("Windows") ? new WindowsFactory() : new MacFactory();
    }
}

When to Use / Avoid

✓ Use When

  • System needs to be independent of how its products are created
  • System works with multiple families of products that must stay compatible
  • You want to enforce constraints between related products
  • You're building a cross-platform library or framework

✕ Avoid When

  • Adding new product types to the family is frequent — requires changing the interface
  • Only one family of products is needed — overkill
  • Products don't need to be related or compatible

Real-World Examples

Pros & Cons

Pros

  • Guarantees product family compatibility
  • Isolates concrete classes from client code
  • Easy to swap entire product families (just change the factory)
  • Open/Closed for new families — add factory without changing client

Cons

  • Adding new product types to a family requires changing the AbstractFactory interface — violates OCP
  • Many interfaces and classes — significant code increase

How Abstract Factory Can Be Broken

⚠ Attack Vectors

  • Cross-family mixing: Nothing stops a client from getting a button from WindowsFactory and a checkbox from MacFactory — producing an inconsistent UI
  • Casting to concrete type: Client casts Button back to WindowsButton and calls platform-specific methods, re-coupling to a concrete class
  • Extending the AbstractFactory interface: Adding a new product type (e.g., createScrollbar()) to the interface forces all existing ConcreteFactories to change — violates OCP
  • Runtime factory switching mid-session: Swapping from WindowsFactory to MacFactory after some products are created leaves a mix of Windows and Mac components alive simultaneously

✓ Prevention

  • Enforce factory per session: Inject the factory once at startup and make it final/immutable. Document that the factory should not be changed at runtime
  • Hide concrete product classes: Keep all ConcreteProduct classes package-private; clients can only see the product interfaces — impossible to downcast
  • Extend via new factory, not new method: Instead of adding methods to the AbstractFactory, create a new extended interface and factory class so existing factories are unaffected
  • Factory registry: Route all factory lookups through a central registry that enforces one factory choice per context, preventing ad-hoc mixing
Java — Break & Fix
// ❌ BREAKING — cross-family mixing
GUIFactory winFactory = new WindowsFactory();
GUIFactory macFactory = new MacFactory();
// Nothing in the type system prevents this!
Button   btn      = winFactory.createButton();   // Windows
Checkbox checkbox = macFactory.createCheckbox(); // Mac — mismatch!

// ✅ FIX — inject factory once, make it immutable
public class Application {
    private final GUIFactory factory; // set once, never changed
    public Application(GUIFactory factory) { this.factory = factory; }
    // All UI components created from single factory — guaranteed compatible
}

// ❌ BREAKING — downcasting defeats abstraction
Button btn = factory.createButton();
((WindowsButton) btn).setWindowsSpecificStyle(); // coupled again!

// ✅ FIX — keep concrete products package-private
// WindowsButton is in package com.ui.windows (not exported)
// Client package can only see com.ui.Button interface

How Other Patterns Relate

Interview Cheat Sheet

  1. What: Creates families of related objects — all from one factory, all guaranteed to be compatible with each other.
  2. How: Interface with one creation method per product type. Multiple ConcreteFactories each implement the full interface for their family. Client uses only the interface.
  3. vs Factory Method: Factory Method = one product via subclassing. Abstract Factory = whole family via composition. Abstract Factory often uses Factory Methods internally.