Abstract Factory Pattern

Learn how to use the Abstract Factory pattern to create families of related objects without specifying their concrete classes, promoting flexibility and consistency in object creation.

Table of Contents

1. Introduction

The Abstract Factory pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. It's particularly useful when you need to ensure that created objects are compatible with each other.

This pattern is one level of abstraction higher than the Factory Method pattern. While Factory Method creates one type of object, Abstract Factory creates families of related objects.

2. Problem Statement

Consider a UI framework that needs to support multiple operating systems (Windows, macOS, Linux). Each OS has different implementations for buttons, checkboxes, and text fields. You need to:

  • Create UI components that are consistent with the operating system
  • Ensure that Windows buttons work with Windows checkboxes, not macOS ones
  • Make it easy to switch between different OS implementations
  • Avoid hardcoding specific implementations
Problem: Without Abstract Factory, you might create incompatible combinations like a Windows button with a macOS checkbox, leading to inconsistent UI behavior.

3. Solution

The Abstract Factory pattern solves this by:

  1. Defining an abstract factory interface with methods for creating each type of product
  2. Creating concrete factories for each family of products
  3. Ensuring that products created by the same factory are compatible
  4. Allowing clients to work with abstract interfaces rather than concrete classes

The client code works only with abstract interfaces, making it independent of the concrete classes it uses.

4. Structure

4.1 Participants

  • AbstractFactory: Declares creation methods for each type of product
  • ConcreteFactory: Implements creation methods to produce concrete products
  • AbstractProduct: Declares interface for a type of product
  • ConcreteProduct: Implements AbstractProduct interface
  • Client: Uses only abstract interfaces

4.2 Class Diagram

classDiagram AbstractFactory <<interface>> ConcreteFactory1 ConcreteFactory2 AbstractProductA <<interface>> AbstractProductB <<interface>> ConcreteProductA1 ConcreteProductA2 ConcreteProductB1 ConcreteProductB2 Client AbstractFactory : +createProductA() AbstractFactory : +createProductB() ConcreteFactory1 : +createProductA() ConcreteFactory1 : +createProductB() ConcreteFactory2 : +createProductA() ConcreteFactory2 : +createProductB() AbstractProductA : +operationA() AbstractProductB : +operationB() ConcreteProductA1 : +operationA() ConcreteProductA2 : +operationA() ConcreteProductB1 : +operationB() ConcreteProductB2 : +operationB() Client : -factory Client : +useProducts() AbstractFactory <|.. ConcreteFactory1 AbstractFactory <|.. ConcreteFactory2 AbstractProductA <|.. ConcreteProductA1 AbstractProductA <|.. ConcreteProductA2 AbstractProductB <|.. ConcreteProductB1 AbstractProductB <|.. ConcreteProductB2 ConcreteFactory1 ..> ConcreteProductA1 : creates ConcreteFactory1 ..> ConcreteProductB1 : creates ConcreteFactory2 ..> ConcreteProductA2 : creates ConcreteFactory2 ..> ConcreteProductB2 : creates Client --> AbstractFactory Client --> AbstractProductA Client --> AbstractProductB

4.3 UI Components Example Diagram

classDiagram GUIFactory <<interface>> WindowsFactory MacOSFactory Button <<interface>> Checkbox <<interface>> WindowsButton WindowsCheckbox MacOSButton MacOSCheckbox Application GUIFactory : +createButton() GUIFactory : +createCheckbox() WindowsFactory : +createButton() WindowsFactory : +createCheckbox() MacOSFactory : +createButton() MacOSFactory : +createCheckbox() Button : +render() Button : +onClick() Checkbox : +render() Checkbox : +onCheck() WindowsButton : +render() WindowsButton : +onClick() WindowsCheckbox : +render() WindowsCheckbox : +onCheck() MacOSButton : +render() MacOSButton : +onClick() MacOSCheckbox : +render() MacOSCheckbox : +onCheck() Application : -button Application : -checkbox Application : +render() GUIFactory <|.. WindowsFactory GUIFactory <|.. MacOSFactory Button <|.. WindowsButton Button <|.. MacOSButton Checkbox <|.. WindowsCheckbox Checkbox <|.. MacOSCheckbox WindowsFactory ..> WindowsButton : creates WindowsFactory ..> WindowsCheckbox : creates MacOSFactory ..> MacOSButton : creates MacOSFactory ..> MacOSCheckbox : creates Application --> GUIFactory Application --> Button Application --> Checkbox

5. Implementation

5.1 Sequence Diagram

sequenceDiagram participant Client participant Application participant GUIFactory as Abstract Factory participant WindowsFactory participant MacOSFactory participant Button participant Checkbox Client->>GUIFactory: Select factory based on OS alt Windows OS Client->>WindowsFactory: new WindowsFactory() WindowsFactory->>Button: createButton() WindowsFactory-->>Button: WindowsButton WindowsFactory->>Checkbox: createCheckbox() WindowsFactory-->>Checkbox: WindowsCheckbox else macOS Client->>MacOSFactory: new MacOSFactory() MacOSFactory->>Button: createButton() MacOSFactory-->>Button: MacOSButton MacOSFactory->>Checkbox: createCheckbox() MacOSFactory-->>Checkbox: MacOSCheckbox end Client->>Application: new Application(factory) Application->>GUIFactory: createButton() GUIFactory-->>Application: Button instance Application->>GUIFactory: createCheckbox() GUIFactory-->>Application: Checkbox instance Client->>Application: render() Application->>Button: render() Application->>Checkbox: render()

5.2 Java Example: UI Components

Step 1: Define Abstract Products
// Abstract Product A
public interface Button {
    void render();
    void onClick();
}

// Abstract Product B
public interface Checkbox {
    void render();
    void onCheck();
}
Step 2: Create Concrete Products
// Windows Products
public class WindowsButton implements Button {
    public void render() {
        System.out.println("Rendering Windows button");
    }
    
    public void onClick() {
        System.out.println("Windows button clicked");
    }
}

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

// macOS Products
public class MacOSButton implements Button {
    public void render() {
        System.out.println("Rendering macOS button");
    }
    
    public void onClick() {
        System.out.println("macOS button clicked");
    }
}

public class MacOSCheckbox implements Checkbox {
    public void render() {
        System.out.println("Rendering macOS checkbox");
    }
    
    public void onCheck() {
        System.out.println("macOS checkbox checked");
    }
}
Step 3: Define Abstract Factory
public interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}
Step 4: Implement Concrete Factories
public class WindowsFactory implements GUIFactory {
    public Button createButton() {
        return new WindowsButton();
    }
    
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

public class MacOSFactory implements GUIFactory {
    public Button createButton() {
        return new MacOSButton();
    }
    
    public Checkbox createCheckbox() {
        return new MacOSCheckbox();
    }
}
Step 5: Client Code
public class Application {
    private Button button;
    private Checkbox checkbox;
    
    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }
    
    public void render() {
        button.render();
        checkbox.render();
    }
}

// Usage
public class Demo {
    public static void main(String[] args) {
        // Choose factory based on OS
        GUIFactory factory;
        String os = System.getProperty("os.name").toLowerCase();
        
        if (os.contains("win")) {
            factory = new WindowsFactory();
        } else if (os.contains("mac")) {
            factory = new MacOSFactory();
        } else {
            throw new RuntimeException("Unsupported OS");
        }
        
        Application app = new Application(factory);
        app.render();
    }
}

6. Use Cases

The Abstract Factory pattern is ideal when:

  • System independence: You need to configure your application with one of multiple families of products
  • Product families: You want to provide a library of products and reveal only their interfaces
  • Consistency: You need to ensure products from one family are used together
  • Cross-platform development: Creating UI components for different operating systems
  • Database abstraction: Supporting multiple database vendors with compatible components

6.1 Real-World Examples

  • Java Swing: Different look-and-feel implementations (Metal, Windows, Motif)
  • Spring Framework: Different data access implementations (JDBC, JPA, MongoDB)
  • Game Development: Different rendering engines (OpenGL, DirectX, Vulkan)

7. Advantages and Disadvantages

7.1 Advantages

  • Isolation: Separates concrete classes from client code
  • Consistency: Ensures products from one family are used together
  • Exchangeability: Easy to switch between product families
  • Single Responsibility: Factory creation logic is centralized
  • Open/Closed Principle: Easy to introduce new product families

7.2 Disadvantages

  • Complexity: Adds many new interfaces and classes
  • Rigidity: Difficult to extend with new product types
  • Overhead: May be overkill for simple scenarios

8. Best Practices

  • Use when you need to ensure products are compatible and used together
  • Consider Factory Method pattern for simpler scenarios with single product types
  • Keep factory interfaces focused on related product families
  • Use dependency injection to provide factories to clients
  • Consider using enums or configuration to select the appropriate factory
  • Document which products belong to which families
Tip: If you find yourself adding many new product types frequently, consider whether Abstract Factory is the right choice. The pattern works best when product families are stable.

Post a Comment

0 Comments