Skip to main content

Abstract Factory Pattern

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


Overview

The Abstract Factory pattern is a "factory of factories." It provides an interface for creating entire families of related or dependent objects. The client works with factories and products through abstract interfaces, making it easy to swap entire families of objects without changing client code.

Key characteristics:

  • Creates families of related objects that are designed to work together
  • Enforces consistency — you can't accidentally mix products from different families
  • Client code depends only on abstract interfaces, never on concrete classes

When to Use

  • Your system needs to work with multiple families of related products
  • You want to ensure that products from the same family are used together
  • Switching between product families at runtime is required
  • You need to enforce constraints between related objects
  • Building cross-platform UIs, multi-database support, or theme systems

How It Works

Structure

AbstractFactory (interface)
├── ConcreteFactory1 → creates ProductA1, ProductB1
└── ConcreteFactory2 → creates ProductA2, ProductB2

AbstractProductA (interface)
├── ConcreteProductA1
└── ConcreteProductA2

AbstractProductB (interface)
├── ConcreteProductB1
└── ConcreteProductB2

Example: Cross-Platform UI Components

// ── Abstract Products ──
public interface Button {
void render();
void onClick(Runnable action);
}

public interface Checkbox {
void render();
boolean isChecked();
}

public interface TextField {
void render();
String getText();
}

// ── Windows Family ──
public class WindowsButton implements Button {
@Override public void render() { System.out.println("[Windows Button]"); }
@Override public void onClick(Runnable action) { action.run(); }
}

public class WindowsCheckbox implements Checkbox {
private boolean checked = false;
@Override public void render() { System.out.println("[Windows Checkbox]"); }
@Override public boolean isChecked() { return checked; }
}

public class WindowsTextField implements TextField {
@Override public void render() { System.out.println("[Windows TextField]"); }
@Override public String getText() { return "windows-input"; }
}

// ── macOS Family ──
public class MacButton implements Button {
@Override public void render() { System.out.println("[Mac Button]"); }
@Override public void onClick(Runnable action) { action.run(); }
}

public class MacCheckbox implements Checkbox {
private boolean checked = false;
@Override public void render() { System.out.println("[Mac Checkbox]"); }
@Override public boolean isChecked() { return checked; }
}

public class MacTextField implements TextField {
@Override public void render() { System.out.println("[Mac TextField]"); }
@Override public String getText() { return "mac-input"; }
}

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

// ── Concrete Factories ──
public class WindowsFactory implements GUIFactory {
@Override public Button createButton() { return new WindowsButton(); }
@Override public Checkbox createCheckbox() { return new WindowsCheckbox(); }
@Override public TextField createTextField() { return new WindowsTextField(); }
}

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

// ── Client Code ──
public class Application {
private final Button button;
private final Checkbox checkbox;
private final TextField textField;

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

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

// Usage — the client never references concrete classes
GUIFactory factory = isWindows() ? new WindowsFactory() : new MacFactory();
Application app = new Application(factory);
app.renderUI();

Factory Method vs Abstract Factory

AspectFactory MethodAbstract Factory
CreatesOne product at a timeFamily of related products
MechanismInheritance (subclass overrides method)Composition (factory object passed to client)
ComplexitySimplerMore complex
ExtensibilityAdd product variantsAdd entire product families
ExamplecreateNotification()createButton(), createCheckbox(), createTextField()

Real-World Examples

Framework/LibraryDescription
javax.xml.parsers.DocumentBuilderFactoryCreates XML parsers (different implementations)
javax.xml.transform.TransformerFactoryCreates XSLT transformers
Java AWT ToolkitToolkit.getDefaultToolkit() creates platform-specific UI peers
Spring BeanFactory / ApplicationContextCreates and manages families of beans

Advantages & Disadvantages

AdvantagesDisadvantages
Isolates concrete classes from client codeAdding new product types requires changing all factories
Ensures consistency among related productsCan lead to a large number of classes
Makes exchanging product families easyIncreases initial complexity
Follows Open/Closed Principle for familiesOverkill for simple object creation
Promotes programming to interfaces

Interview Questions

Q1: What is the Abstract Factory pattern and how does it differ from the Factory pattern?

The Abstract Factory pattern creates families of related objects without specifying their concrete classes, often grouped by theme or platform. It differs from the Factory pattern in scope: a Factory Method creates one product, while an Abstract Factory creates a suite of related products designed to work together. Abstract Factory is a "factory of factories."

Q2: Can you describe a real-world scenario where you would use the Abstract Factory pattern?

Building a cross-platform UI toolkit. The Abstract Factory interface defines methods to create buttons, text fields, and checkboxes. Each platform (Windows, macOS, Linux) has its own concrete factory that produces platform-specific components. The client code works with the abstract factory interface, so switching the entire UI to a different platform requires changing only the factory — no client code modifications.

Q3: How would you implement the Abstract Factory pattern in Java?

Define an abstract factory interface with creation methods for each product type. Create concrete factory classes for each product family, implementing all creation methods. Each factory class instantiates its family-specific products. The client receives a factory through its constructor (dependency injection) and calls the creation methods through the abstract interface.

Q4: What are the advantages of using the Abstract Factory pattern?

It promotes scalability by allowing new product families to be added without modifying existing code. It ensures consistency among products designed to work together. It isolates concrete classes from clients, supporting the Dependency Inversion Principle. And it makes switching between product families trivial.

Q5: How can the Abstract Factory pattern support scalability in large systems?

New product families can be added by creating new concrete factory classes without modifying existing code or factories. The client code remains unchanged because it depends only on the abstract interface. This separation allows different teams to develop different product families independently, and the system can be extended as requirements grow.