1. Definition

The Abstract Factory Design Pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

2. Problem Statement

Imagine developing a cross-platform GUI library where different platforms (like Windows, Linux, macOS) have different appearances and behaviors for GUI elements such as buttons and checkboxes. How can you ensure that the components used by the client are consistent with the platform they run on, without hardcoding for each platform?

3. Solution

The Abstract Factory pattern suggests segregating the product creation logic into multiple factory classes, each corresponding to a specific family (or platform). You define an abstract factory interface with multiple methods for creating products, then implement this interface in each concrete factory class corresponding to the platforms.

4. Real-World Use Cases

1. GUI libraries that need to be consistent across multiple platforms.

2. Database connectivity where different databases might have different ways to establish connections and perform CRUD operations.

3. Themes in applications where each theme might have different visual elements.

5. Implementation Steps

1. Define abstract product interfaces for all types of products.

2. Implement concrete product classes for all platform families.

3. Create an abstract factory interface that declares factory methods for all product types.

4. Implement concrete factory classes for each platform.

5. Client code should use the abstract factory and product interfaces only.

6. Implementation

// Abstract product interfaces
public interface Button {
    void click();
}

public interface CheckBox {
    void check();
}

// Concrete product implementations for Windows
public class WindowsButton implements Button {
    @Override
    public void click() {
        System.out.println("Windows button clicked!");
    }
}

public class WindowsCheckBox implements CheckBox {
    @Override
    public void check() {
        System.out.println("Windows checkbox checked!");
    }
}

// Concrete product implementations for Linux
public class LinuxButton implements Button {
    @Override
    public void click() {
        System.out.println("Linux button clicked!");
    }
}

public class LinuxCheckBox implements CheckBox {
    @Override
    public void check() {
        System.out.println("Linux checkbox checked!");
    }
}

// Abstract factory interface
public interface GUIFactory {
    Button createButton();
    CheckBox createCheckBox();
}

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

    @Override
    public CheckBox createCheckBox() {
        return new WindowsCheckBox();
    }
}

public class LinuxFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new LinuxButton();
    }

    @Override
    public CheckBox createCheckBox() {
        return new LinuxCheckBox();
    }
}

// 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.click();
        checkBox.check();
    }

    public static void main(String[] args) {
        GUIFactory factory;
        if (System.getProperty("os.name").equals("Windows")) {
            factory = new WindowsFactory();
        } else {
            factory = new LinuxFactory();
        }

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

Output:

Assuming we run on a Linux system:
Linux button clicked!
Linux checkbox checked!

Explanation:

In this example, we used the Abstract Factory pattern to produce GUI components that are consistent with the underlying platform. The Application class doesn't depend on concrete classes like WindowsButton or LinuxCheckBox. Instead, it uses the abstract factory and product interfaces, making it easy to extend to new platforms without changing the client code.

7. When to use?

Use the Abstract Factory pattern when:

1. The system needs to remain independent from the way its objects are created, composed, and represented.

2. The system is configured with multiple families of products or requires a family of products to be used together.

3. You want to provide a library of products and reveal only their interfaces, not their implementations.