AlgoMaster Logo

Abstract Factory Design Pattern

Ashish

Ashish Pratap Singh

6 min read

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

It’s particularly useful in situations where:

  • You need to create objects that must be used together and are part of a consistent family (e.g., GUI elements like buttons, checkboxes, and menus).
  • Your system must support multiple configurations, environments, or product variants (e.g., light vs. dark themes, Windows vs. macOS look-and-feel).
  • You want to enforce consistency across related objects, ensuring that they are all created from the same factory.

The Abstract Factory Pattern encapsulates object creation into factory interfaces.

Each concrete factory implements the interface and produces a complete set of related objects. This ensures that your code remains extensible, consistent, and loosely coupled to specific product implementations.

Let’s walk through a real-world example to see how we can apply the Abstract Factory Pattern to build a system that’s flexible, maintainable, and able to support multiple interchangeable product families without conditional logic.

1. The Problem: Platform-Specific UI

Imagine you're building a cross-platform desktop application that must support both Windows and macOS.

To provide a good user experience, your application should render native-looking UI components for each operating system like:

  • Buttons
  • Checkboxes
  • Text fields
  • Menus

Naive Implementation: Conditional UI Component Instantiation

In your first attempt, you might implement platform-specific UI components like this:

Windows UI Elements

MacOS UI Elements

Then, in your application logic, you end up doing something like:

Why This Approach Breaks Down

While this setup looks simple for two UI components on two platforms, it quickly becomes a nightmare as your app grows.

1. Tight Coupling to Concrete Classes

Your main application logic is tightly bound to platform-specific classes (WindowsButtonMacOSCheckbox, etc.). This means everywhere you create UI components, you must check the OS manually.

2. No Abstraction or Polymorphism

You can’t treat buttons or checkboxes generically. There’s no common interface like Button or Checkbox to work with.

3. Code Duplication and Repetition

Each platform-specific block replicates similar logic — instantiating buttons, checkboxes, menus, etc. You'll repeat this conditional branching throughout your codebase.

4. Scalability Issues

What happens when you:

  • Add a new platform (e.g., Linux)?
  • Add a new component (e.g., TextFieldSliderMenuBar)?

You’ll have to:

  • Add new concrete classes for each platform
  • Modify every place in your code where platform-specific logic appears
  • Risk breaking existing behavior

5. Violation of Open/Closed Principle

Any new variant requires modifying existing code. Your UI creation logic is not open for extension but very much open to breaking changes.

What We Really Need

We need a way to:

  • Group related components (button, checkbox, etc.) by platform
  • Encapsulate platform-specific creation logic into a factory
  • Work with UI components polymorphically, regardless of platform
  • Easily add new families of UI elements without modifying the application’s core logic

This is where the Abstract Factory Pattern comes in.

2. What is Abstract Factory

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

In our case, we want to create a family of UI components (like ButtonCheckboxTextField, etc.) that look and behave differently on different platforms (such as Windows or macOS) but expose the same interface to the application.

Class Diagram

1. Abstract Factory (GUIFactory)

  • Defines a common interface for creating a family of related products.
  • Typically includes factory methods like createButton()createCheckbox()createTextField(), etc.
  • Clients rely on this interface to create objects without knowing their concrete types.

2. Concrete Factory (WindowsFactoryMacOSFactory)

  • Implement the abstract factory interface.
  • Create concrete product variants that belong to a specific family or platform.
  • Each factory ensures that all components it produces are compatible (i.e., belong to the same platform/theme).

3. Abstract Product (ButtonCheckbox)

  • Define the interfaces or abstract classes for a set of related components.
  • All product variants for a given type (e.g., WindowsButtonMacOSButton) will implement these interfaces.

4. Concrete Product (WindowsButtonMacOSCheckbox)

  • Implement the abstract product interfaces.
  • Contain platform-specific logic and appearance for the components.

5. Client (Application)

  • Uses the abstract factory and abstract product interfaces.
  • Is completely unaware of the concrete classes it is using — it only interacts with the factory and product interfaces.
  • Can switch entire product families (e.g., from Windows to macOS) by changing the factory without touching UI logic.

3. Implementing Abstract Factory

Let’s implement a system where our app can generate native-looking UI components (buttons, checkboxes) for Windows and macOS without hardcoding platform checks or duplicating logic.

1. Define Abstract Product Interfaces

We start by defining the product interfaces used by the client.

Button

Checkbox

2. Create Concrete Product Classes

These implement the platform-specific logic.

Windows Components

macOS Components

3. Define the Abstract Factory

This is the interface for creating families of related products.

4. Implement Concrete Factories

Each factory creates platform-specific component variants.

WindowsFactory

MacOSFactory

5. Client Code – Use Abstract Interfaces Only

The client code uses the factory to create UI components. It doesn't care which OS it is dealing with.

6. Application Entry Point

Output (on macOS)

Output (on Windows)

What We Achieved

  • Platform independence: Application code never references platform-specific classes
  • Consistency: Buttons and checkboxes always match the selected OS style
  • Open/Closed Principle: Add support for Linux or Android without modifying existing factories or components
  • Testability & Flexibility: Factories can be swapped easily for testing or theming