Abstract Factory Pattern

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 useful when you need to create multiple families of related objects or ensure that objects created by a factory are compatible and work together seamlessly.

Important Features

  • Abstract Factory: An interface or abstract class declaring methods for creating abstract products.
  • Concrete Factories: Implement the factory methods to create specific product families.
  • Abstract Products: Interfaces for different types of products.
  • Concrete Products: Specific implementations of the product interfaces, grouped into families.
  • Client Code: Uses the abstract factory and product interfaces, not concrete classes.

Sample Implementation

// Abstract products
interface Button {
    void render();
}

interface Checkbox {
    void check();
}

// Concrete products for Windows
class WindowsButton implements Button {
    public void render() {
        System.out.println("Rendering Windows Button");
    }
}

class WindowsCheckbox implements Checkbox {
    public void check() {
        System.out.println("Checking Windows Checkbox");
    }
}

// Concrete products for Mac
class MacButton implements Button {
    public void render() {
        System.out.println("Rendering Mac Button");
    }
}

class MacCheckbox implements Checkbox {
    public void check() {
        System.out.println("Checking Mac Checkbox");
    }
}

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

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

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

// Client code
public class Main {
    public static void main(String[] args) {
        GUIFactory factory = new WindowsFactory();
        Button button = factory.createButton();
        Checkbox checkbox = factory.createCheckbox();
        button.render(); // Output: Rendering Windows Button
        checkbox.check(); // Output: Checking Windows Checkbox

        factory = new MacFactory();
        button = factory.createButton();
        checkbox = factory.createCheckbox();
        button.render(); // Output: Rendering Mac Button
        checkbox.check(); // Output: Checking Mac Checkbox
    }
}Code language: PHP (php)

Use case and Implementation

Creating families of objects for different banking services, such as account management and loan processing, ensuring they work together seamlessly.

In the context of creating families of objects for different banking services, such as account management and loan processing, let’s delve into how the Abstract Factory Pattern can be applied:

  1. Problem Statement: You need to create families of related objects, such as different types of accounts (SavingsAccount, CheckingAccount) and their corresponding services (LoanProcessingService, AccountManagementService), ensuring that objects within each family are compatible and work together seamlessly.
  2. Solution: Implement an abstract factory interface (BankServiceFactory) that declares a set of methods for creating each distinct product family (Account and BankService). Concrete subclasses (BankAFactory, BankBFactory) then implement these methods to create specific product objects (SavingsAccount, CheckingAccount, LoanProcessingService, etc.) that belong to the same family.
//AbstractFactoryDemo.java
// Abstract product: Account interface
interface Account {
    void deposit(double amount);
    void withdraw(double amount);
    double getBalance();
}
// Abstract product: BankService interface
interface BankService {
    void process();
}
// Concrete product: SavingsAccount class
class SavingsAccount implements Account {
    private double balance;
    @Override
    public void deposit(double amount) {
        balance += amount;
    }
    @Override
    public void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
        } else {
            System.out.println("Insufficient balance.");
        }
    }
    @Override
    public double getBalance() {
        return balance;
    }
}
// Concrete product: CheckingAccount class
class CheckingAccount implements Account {
    private double balance;
    @Override
    public void deposit(double amount) {
        balance += amount;
    }
    @Override
    public void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
        } else {
            System.out.println("Insufficient balance.");
        }
    }
    @Override
    public double getBalance() {
        return balance;
    }
}
// Concrete product: LoanProcessingService class
class LoanProcessingService implements BankService {
    @Override
    public void process() {
        System.out.println("Processing loan application...");
        // Additional loan processing logic
    }
}
// Concrete product: AccountManagementService class
class AccountManagementService implements BankService {
    @Override
    public void process() {
        System.out.println("Managing account...");
        // Additional account management logic
    }
}
// Abstract factory: BankServiceFactory interface
interface BankServiceFactory {
    Account createAccount();
    BankService createBankService();
}
// Concrete factory A: BankAFactory class
class BankAFactory implements BankServiceFactory {
    @Override
    public Account createAccount() {
        return new SavingsAccount(); // Bank A uses SavingsAccount
    }
    @Override
    public BankService createBankService() {
        return new LoanProcessingService(); 
        // Bank A offers LoanProcessingService
    }
}
// Concrete factory B: BankBFactory class
class BankBFactory implements BankServiceFactory {
    @Override
    public Account createAccount() {
        return new CheckingAccount(); // Bank B uses CheckingAccount
    }
    @Override
    public BankService createBankService() {
        return new AccountManagementService(); 
        // Bank B offers AccountManagementService
    }
}
class AbstractFactoryDemo {
    public static void main(String[] args) {
        // Assume bank selection based on user input or configuration
        String bankType = "BankA"; 
        // This can be input from the user or configuration
        BankServiceFactory factory;
        // Determine which factory to use based on bankType
        if (bankType.equalsIgnoreCase("BankA")) {
            factory = new BankAFactory();
        } else if (bankType.equalsIgnoreCase("BankB")) {
            factory = new BankBFactory();
        } else {
            throw new IllegalArgumentException("Unknown bank type");
        }
        // Create products using the factory
        Account account = factory.createAccount();
        BankService service = factory.createBankService();
        // Use the created products
        account.deposit(1000);
        service.process();
        System.out.println("Current balance: " + account.getBalance());
    }
}

/*
D:\>javac AbstractFactoryDemo.java

D:\>java AbstractFactoryDemo
Processing loan application...
Current balance: 1000.0
*/

Pros

  • Consistency: Ensures products from the same family are used together (e.g., Windows buttons with Windows checkboxes).
  • Loose Coupling: Client code depends on abstractions, not concrete classes.
  • Extensibility: Easy to add new product families by creating new concrete factories.

Cons

  • Complexity: Adds more classes and interfaces, increasing code complexity.
  • Scalability Issues: Adding new product types requires modifying the abstract factory and all concrete factories.

When to Use

  • When a system needs to create families of related objects (e.g., UI components for different operating systems).
  • When you want to enforce consistency among products.
  • When you need to swap entire product families at runtime (e.g., switching between themes).

The Abstract Factory Pattern is valuable when you need to create families of related or dependent objects and ensure they work together seamlessly, such as in banking systems where different types of accounts and services need to be managed consistently across different banks or financial institutions.

Scroll to Top