The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It ensures that products created together are compatible and belong to the same variant (e.g., Windows UI vs macOS UI).

Intent and Motivation

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

Motivation: Consider a cross-platform GUI toolkit. A Windows button must pair with a Windows checkbox; a macOS button must pair with a macOS checkbox. If clients call constructors directly, they might accidentally mix Windows buttons with macOS scrollbars. Abstract Factory encapsulates the entire family creation behind one interface, guaranteeing consistency within a family while allowing entire families to be swapped at runtime.

This pattern is essential when systems must support multiple “themes,” “platforms,” or “configurations” where objects are interdependent.

Structure (UML-like)

  ┌─────────────────────┐
│   AbstractFactory   │
├─────────────────────┤
│ + createProductA()  │
│ + createProductB()  │
└──────────▲──────────┘
           │ implements
┌──────────┴──────────┐         ┌─────────────────┐
│ ConcreteFactory1    │ creates │  ProductA1      │
│                     │ ──────► │  ProductB1      │
├─────────────────────┤         └─────────────────┘
│ + createProductA()  │
│ + createProductB()  │
└─────────────────────┘

┌─────────────────────┐         ┌─────────────────┐
│ ConcreteFactory2    │ creates │  ProductA2      │
│                     │ ──────► │  ProductB2      │
└─────────────────────┘         └─────────────────┘
  

Participants:

  • AbstractFactory — declares creation methods for each product in the family.
  • ConcreteFactory — implements creation methods to produce a specific family variant.
  • AbstractProduct — base interfaces for each product type (ProductA, ProductB).
  • ConcreteProduct — specific implementations belonging to a family.

Java Example

  // Abstract Products
interface Button { void render(); }
interface Checkbox { void toggle(); }

// Concrete Products — Windows family
class WinButton implements Button {
    public void render() { System.out.println("Windows button rendered"); }
}
class WinCheckbox implements Checkbox {
    public void toggle() { System.out.println("Windows checkbox toggled"); }
}

// Concrete Products — macOS family
class MacButton implements Button {
    public void render() { System.out.println("macOS button rendered"); }
}
class MacCheckbox implements Checkbox {
    public void toggle() { System.out.println("macOS checkbox toggled"); }
}

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

class WinFactory implements GUIFactory {
    public Button createButton() { return new WinButton(); }
    public Checkbox createCheckbox() { return new WinCheckbox(); }
}

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

// Client
class Application {
    private Button button;
    private Checkbox checkbox;

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

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

// Usage
Application app = new Application(new MacFactory());
app.paint();
  

JavaScript Example

  // Abstract Factory as object with factory methods
const winFactory = {
  createButton() {
    return { render: () => console.log('Win button') };
  },
  createCheckbox() {
    return { toggle: () => console.log('Win checkbox') };
  }
};

const macFactory = {
  createButton() {
    return { render: () => console.log('Mac button') };
  },
  createCheckbox() {
    return { toggle: () => console.log('Mac checkbox') };
  }
};

function createUI(factory) {
  const button = factory.createButton();
  const checkbox = factory.createCheckbox();
  return {
    render() {
      button.render();
      checkbox.toggle();
    }
  };
}

const ui = createUI(
  process.platform === 'darwin' ? macFactory : winFactory
);
ui.render();
  

Real-World Use Cases

Framework / System Usage
javax.swing / AWT UIManager and look-and-feel classes create coordinated widget families per theme.
Spring Framework @Configuration classes act as abstract factories, producing related beans (DataSource + TransactionManager + EntityManager).
AWS SDK Service clients and their associated serializers/config objects are created as compatible families per region/credential profile.
Unity / Unreal Asset bundles and material/shader families must be consistent within a rendering pipeline variant.
Database drivers JDBC Connection, Statement, and ResultSet implementations from one driver are always compatible.

Pros and Cons

Pros Cons
Ensures product compatibility within a family Adding a new product type requires changing every concrete factory
Isolates concrete classes from client code High complexity — many interfaces and classes
Easy to swap entire product families at runtime Can be overkill for simple object creation
Promotes consistency across related objects Tight coupling between products in the same family
Follows Open/Closed Principle for new families Testing requires mocking entire factory interfaces

When to Use vs When NOT to Use

Use when:

  • A system must be independent of how its products are created, composed, and represented.
  • The system must work with multiple families of products (themes, platforms, databases).
  • Product families must be used together and must not be mixed.
  • You want to provide a library of products and reveal only their interfaces.

Do NOT use when:

  • You only need to create a single type of object (use Factory Method or Simple Factory).
  • Product families are unlikely to change or multiply.
  • The construction process is step-by-step and complex (use Builder).
  • Dependency injection already wires compatible beans without a custom factory hierarchy.

Common Mistakes

  1. Mixing families — bypassing the factory and instantiating concrete products directly breaks compatibility guarantees.
  2. God factory — one abstract factory with dozens of create* methods becomes unmaintainable; split into smaller factories.
  3. Confusing with Factory Method — Abstract Factory creates families; Factory Method creates one product via subclassing.
  4. Not making products implement common interfaces — clients cannot treat products polymorphically.
  5. Hard-coding factory selection — use configuration or environment detection to choose the factory dynamically.
  • Factory Method — Abstract Factory is often implemented with Factory Methods; one method per product type.
  • Builder — focuses on step-by-step construction of one complex object; can coexist with Abstract Factory.
  • Prototype — can be used inside concrete factories to clone preconfigured product prototypes.
  • Singleton — concrete factories are often singletons (one Windows factory, one Mac factory).
  • Facade — can wrap an Abstract Factory to simplify access for clients that need only a subset of products.