Skip to main content

Abstract Factory Pattern

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

Problem

You need to create families of related objects that must be used together, and you want to ensure that clients use products from the same family.

Common Scenarios:

  • UI toolkit that supports multiple look-and-feels (Windows, Mac, Linux)
  • Database abstraction layer supporting multiple databases
  • Document creation supporting multiple formats
  • Cross-platform application development

Design Principles Applied

  1. Program to an interface, not an implementation
  2. Favor composition over inheritance
  3. Depend on abstractions, not concrete classes

UML Diagram

Implementation

Example: UI Toolkit (Cross-Platform)

// Abstract Products
public interface Button {
void paint();
void click();
}

public interface Checkbox {
void paint();
void check();
}

// Concrete Products - Windows
public class WindowsButton implements Button {
@Override
public void paint() {
System.out.println("Rendering Windows button");
}

@Override
public void click() {
System.out.println("Windows button clicked");
}
}

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

@Override
public void check() {
System.out.println("Windows checkbox checked");
}
}

// Concrete Products - Mac
public class MacButton implements Button {
@Override
public void paint() {
System.out.println("Rendering Mac button");
}

@Override
public void click() {
System.out.println("Mac button clicked");
}
}

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

@Override
public void check() {
System.out.println("Mac checkbox checked");
}
}

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

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

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

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

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

// Client Code
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();
}
}

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

if (osName.contains("mac")) {
factory = new MacFactory();
} else {
factory = new WindowsFactory();
}

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

Benefits

Consistency - Ensures products from same family are used together ✅ Isolation - Separates concrete classes from client code ✅ Easy to exchange - Switch entire product families easily ✅ Promotes consistency - Products designed to work together

Drawbacks

Complexity - Requires many interfaces and classes ❌ Difficult to extend - Adding new products requires changing all factories ❌ Overhead - More code to maintain

When to Use

Use Abstract Factory When:

  • System should be independent of how products are created
  • System should be configured with multiple families of products
  • Family of related products must be used together
  • You want to provide a library of products without exposing implementations

Don't Use When:

  • You have only one product family
  • Adding new products is frequent (consider other patterns)

Summary

Abstract Factory creates families of related objects without specifying their concrete classes, ensuring products work together.

Key Takeaway: Use Abstract Factory when you need to create families of related objects that must be used together.