CLOSE

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.

In simple terms:

You use it when you have multiple factories, each responsible for producing objects that are meant to work together.

This pattern is an extension of the Factory Design Pattern, designed to deal with multiple factories that create products belonging to different families.

The Abstract Factory Design Pattern provides a way to encapsulate a group of factories with a common theme without specifying their concrete classes.

  • The abstract factory is a super factory that creates other factories. It is also called the factory of factories.

Real-Life Analogy

Imagine a furniture shop:

  • Furniture Styles (Families): Modern, Victorian.
  • Products (Related Objects): Chair, Sofa.
  • Each furniture style has its own factory to create matching chairs and sofas.

Real-World Example

Imagine a cross-platform UI toolkit that creates buttons and checkboxes. The abstract factory can help create UI elements for different platforms (Windows, macOS, Linux) without changing the client code.

Problem: Without Abstract Factory

You might do something like this:

#include <iostream>
using namespace std;

class Button {
public:
    virtual void paint() = 0;
};

class WindowsButton : public Button {
public:
    void paint() override {
        cout << "Painting Windows style button\n";
    }
};

class MacButton : public Button {
public:
    void paint() override {
        cout << "Painting Mac style button\n";
    }
};

class Checkbox {
public:
    virtual void paint() = 0;
};

class WindowsCheckbox : public Checkbox {
public:
    void paint() override {
        cout << "Painting Windows style checkbox\n";
    }
};

class MacCheckbox : public Checkbox {
public:
    void paint() override {
        cout << "Painting Mac style checkbox\n";
    }
};

// Somewhere in your UI code
void createUI(const string& os) {
    Button* btn;
    Checkbox* cb;

    if (os == "Windows") {
        btn = new WindowsButton();
        cb = new WindowsCheckbox();
    } else if (os == "Mac") {
        btn = new MacButton();
        cb = new MacCheckbox();
    } else {
        btn = nullptr;
        cb = nullptr;
    }

    if (btn) btn->paint();
    if (cb) cb->paint();

    delete btn;
    delete cb;
}

int main() {
    createUI("Windows");
    createUI("Mac");
    return 0;
}

What’s the problem here?

  • The createUI function has to know all the concrete classes (WindowsButton, MacCheckbox, etc.).
  • If you want to support a new OS (say Linux), you need to modify the createUI function, violating the Open/Closed Principle (code should be open to extension but closed for modification).
  • The client code is cluttered with if-else or switch statements, making it hard to maintain and error-prone.
  • No guarantee that the buttons and checkboxes come from the same family (e.g., mixing a Windows button with a Mac checkbox might happen accidentally).

How Abstract Factory Solves This Problem

Step 1: Create an Abstract Factory Interface

class GUIFactory {
public:
    virtual Button* createButton() = 0;
    virtual Checkbox* createCheckbox() = 0;
    virtual ~GUIFactory() {}
};

Step 2: Implement Concrete Factories for each OS

class WindowsFactory : public GUIFactory {
public:
    Button* createButton() override {
        return new WindowsButton();
    }
    Checkbox* createCheckbox() override {
        return new WindowsCheckbox();
    }
};

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

Step 3: Use the Factory to create UI components without if-else

void createUI(GUIFactory* factory) {
    Button* btn = factory->createButton();
    Checkbox* cb = factory->createCheckbox();

    btn->paint();
    cb->paint();

    delete btn;
    delete cb;
}

int main() {
    GUIFactory* factory;

    // For Windows UI
    factory = new WindowsFactory();
    createUI(factory);
    delete factory;

    // For Mac UI
    factory = new MacFactory();
    createUI(factory);
    delete factory;

    return 0;
}

Why is this better?

  • The client code (createUI) is decoupled from concrete classes.
  • You can add new OS support (like Linux) simply by creating a new concrete factory without touching createUI.
  • You avoid mixing components from different OS families, because the factory ensures you get matching components.
  • The code respects the Open/Closed Principle — extend by adding new factories, don’t modify existing ones.
  • It’s easier to maintain and scale.

Key Components of the Abstract Factory Pattern

  1. Abstract Factory Interface
    1. Declares creation methods for each kind of product.
  2. Concrete Factories
    1. Implement the abstract factory interface and create concrete product objects.
  3. Abstract products
    1. Declare interfaces for different kinds of products.
  4. Concrete Products
    1. Implement the abstract product interfaces
  5. Client
    1. Use only the abstract factory and abstract products, unaware of concrete implementations.

Key Concepts

What Is It?

  • It is a factory of factories.
  • Instead of creating one type of product, an abstract factory creates related families of products.

Why Use It?

  • To group object creation logic for families of related products.
  • To ensure consistency among products that belong to the same family.

Understanding the Abstract Factory Pattern

The Abstract Factory Pattern is a design pattern used to create families of related objects. The key idea is to:

  • Provide a way to group related objects (products) together.
  • Allow the client code to create objects without knowing their exact classes.

This pattern ensures consistency across related objects and promotes loose coupling between object creation and the client.

Organizing Related Objects:

  • Imagine you have a software system where you need to create groups of related objects, such as different types of shapes or colors. The Abstract Factory Pattern helps you organize the creation of these related objects efficiently.

Set of Rules for Creation:

  • Instead of directly creating specific objects, the Abstract Factory Pattern provides a set of rules or instructions for creating different types of objects within a family. These rules abstract away the specific details of each object, allowing for a unified creation process.

Flexibility and Switching:

  • By following these rules, you can easily switch between different types of objects within the same family without needing to know the exact details of each object. This flexibility makes it easier to adapt to changes or switch implementations seamlessly.

Comparison with the Factory Pattern

  1. Layer of Abstraction:
    • While the Factory Pattern focuses on creating individual objects, the Abstract Factory Pattern adds another layer of abstraction by dealing with families of related objects. This higher level of abstraction allows for more complex object creation scenarios.
  2. Super-Factory Concept:
    • In the Abstract Factory Pattern, there's the concept of a super-factory, which acts as a higher-level entity responsible for creating other factories. These factories, in turn, produce objects belonging to specific families.
  3. Framework for General Patterns:
    • The Abstract Factory Pattern provides a framework for creating objects that follow a general pattern. This framework hides the complexities of object creation and allows for consistent creation of related objects.
  4. Runtime Coupling:
    • At runtime, the abstract factory is coupled with a concrete factory that corresponds to the desired type of objects to be created. This dynamic coupling ensures that the correct objects are created based on the current context or requirements.

Examples

Example 1

Let's illustrate the Abstract Factory Pattern with an example scenario of creating different types of electronic devices, including laptops and smartphones, using an abstract factory.

1️⃣ Abstract Factory:

  • Definition: An interface or abstract class that declares methods for creating related products.
  • Purpose: To define a blueprint for factories that produce products in a specific family.
  • Example:
class ElectronicDeviceFactory {
public:
    virtual Laptop* createLaptop() = 0;
    virtual Smartphone* createSmartphone() = 0;
};

2️⃣ Concrete Factories:

  • Definition: Implementations of the abstract factory interface, responsible for creating specific products for a family.
  • Purpose: To produce objects belonging to a particular family (e.g., Apple, Samsung).
  • Example:
class AppleFactory : public ElectronicDeviceFactory {
public:
    Laptop* createLaptop() override {
        return new MacBook();
    }

    Smartphone* createSmartphone() override {
        return new iPhone();
    }
};

class SamsungFactory : public ElectronicDeviceFactory {
public:
    Laptop* createLaptop() override {
        return new GalaxyBook();
    }

    Smartphone* createSmartphone() override {
        return new GalaxyS();
    }
};

3️⃣ Abstract Products:

  • Definition: Interfaces or abstract classes for the products created by the factories.
  • Purpose: To define a common API for each type of product, ensuring the client code can work with any product family.
  • Example:
class Laptop {
public:
    virtual void displayInfo() = 0;
};

class Smartphone {
public:
    virtual void displayInfo() = 0;
};

4️⃣ Concrete Products:

  • Definition: Specific implementations of the abstract products.
  • Purpose: To represent the actual objects created by the concrete factories.
  • Example:
class MacBook : public Laptop {
public:
    void displayInfo() override {
        cout << "This is a MacBook laptop." << endl;
    }
};

class iPhone : public Smartphone {
public:
    void displayInfo() override {
        cout << "This is an iPhone smartphone." << endl;
    }
};

class GalaxyBook : public Laptop {
public:
    void displayInfo() override {
        cout << "This is a Samsung GalaxyBook laptop." << endl;
    }
};

class GalaxyS : public Smartphone {
public:
    void displayInfo() override {
        cout << "This is a Samsung GalaxyS smartphone." << endl;
    }
};

5️⃣ Client:

  • Definition: Code that uses the abstract factory to create products. It interacts only with the abstract factory and product interfaces, not the concrete implementations.
  • Purpose: To remain decoupled from the specifics of object creation and product families.
  • Example:
void clientCode(ElectronicDeviceFactory* factory) {
    Laptop* laptop = factory->createLaptop();
    Smartphone* smartphone = factory->createSmartphone();

    laptop->displayInfo();
    smartphone->displayInfo();

    delete laptop;
    delete smartphone;
}

int main() {
    ElectronicDeviceFactory* appleFactory = new AppleFactory();
    ElectronicDeviceFactory* samsungFactory = new SamsungFactory();

    cout << "Client: Using Apple factory" << endl;
    clientCode(appleFactory);

    cout << endl;

    cout << "Client: Using Samsung factory" << endl;
    clientCode(samsungFactory);

    delete appleFactory;
    delete samsungFactory;

    return 0;
}

Complete Code

#include <iostream>

using namespace std;

// Abstract Products
class Laptop {
public:
    virtual void displayInfo() = 0;
    virtual ~Laptop() {}
};

class Smartphone {
public:
    virtual void displayInfo() = 0;
    virtual ~Smartphone() {}
};

// Concrete Products
class MacBook : public Laptop {
public:
    void displayInfo() override {
        cout << "This is a MacBook laptop." << endl;
    }
};

class iPhone : public Smartphone {
public:
    void displayInfo() override {
        cout << "This is an iPhone smartphone." << endl;
    }
};

class GalaxyBook : public Laptop {
public:
    void displayInfo() override {
        cout << "This is a Samsung GalaxyBook laptop." << endl;
    }
};

class GalaxyS : public Smartphone {
public:
    void displayInfo() override {
        cout << "This is a Samsung GalaxyS smartphone." << endl;
    }
};

// Abstract Factory
class ElectronicDeviceFactory {
public:
    virtual Laptop* createLaptop() = 0;
    virtual Smartphone* createSmartphone() = 0;
    virtual ~ElectronicDeviceFactory() {}
};

// Concrete Factories
class AppleFactory : public ElectronicDeviceFactory {
public:
    Laptop* createLaptop() override {
        return new MacBook();
    }

    Smartphone* createSmartphone() override {
        return new iPhone();
    }
};

class SamsungFactory : public ElectronicDeviceFactory {
public:
    Laptop* createLaptop() override {
        return new GalaxyBook();
    }

    Smartphone* createSmartphone() override {
        return new GalaxyS();
    }
};

// Client
void clientCode(ElectronicDeviceFactory* factory) {
    Laptop* laptop = factory->createLaptop();
    Smartphone* smartphone = factory->createSmartphone();

    laptop->displayInfo();
    smartphone->displayInfo();

    delete laptop;
    delete smartphone;
}

int main() {
    ElectronicDeviceFactory* appleFactory = new AppleFactory();
    ElectronicDeviceFactory* samsungFactory = new SamsungFactory();

    cout << "Client: Using Apple factory" << endl;
    clientCode(appleFactory);

    cout << endl;

    cout << "Client: Using Samsung factory" << endl;
    clientCode(samsungFactory);

    delete appleFactory;
    delete samsungFactory;

    return 0;
}

In this complete code:

  • Abstract product classes (Laptop and Smartphone) define interfaces for the different types of electronic devices.
  • Concrete product classes (MacBook, iPhone, GalaxyBook, and GalaxyS) implement specific device models.
  • The abstract factory (ElectronicDeviceFactory) provides methods to create laptops and smartphones.
  • Concrete factories (AppleFactory and SamsungFactory) implement the creation logic for Apple and Samsung devices respectively.
  • The client code demonstrates how to use different factories to create families of related devices without needing to know their concrete types.

Example 2

                   +--------------------------+
                   |      Client (App)        |
                   +--------------------------+
                             |
                             v
                 +-------------------------------+
                 | GUIFactory (Abstract Factory) |
                 +-------------------------------+
                             |
          +------------------+------------------+
          |                                     |
+--------------------+                  +--------------------+
| WindowsFactory     |                  | MacFactory         |
| (Concrete Factory) |                  | (Concrete Factory) |
+--------------------+                  +--------------------+
          |                                      |
   +---------------+                          +---------------+
   |               |                          |               |
+-----------+   +-----------+            +-----------+  +-----------+
| WindowsButton |  | WindowsCheckbox |      | MacButton  |  | MacCheckbox |
| (Concrete Product)  |  | (Concrete Product) |  | (Concrete Product) |  | (Concrete Product) |
+-----------+  +-----------+            +-----------+  +-----------+

1️⃣ Abstract Product Interfaces

// Abstract Button
class Button {
public:
    virtual void click() const = 0;
    virtual ~Button() = default;
};

// Abstract Checkbox
class Checkbox {
public:
    virtual void check() const = 0;
    virtual ~Checkbox() = default;
};

2️⃣ Concrete Products

// Concrete Windows Button
class WindowsButton : public Button {
public:
    void click() const override {
        std::cout << "Windows Button clicked.\n";
    }
};

// Concrete Windows Checkbox
class WindowsCheckbox : public Checkbox {
public:
    void check() const override {
        std::cout << "Windows Checkbox checked.\n";
    }
};

// Concrete Mac Button
class MacButton : public Button {
public:
    void click() const override {
        std::cout << "Mac Button clicked.\n";
    }
};

// Concrete Mac Checkbox
class MacCheckbox : public Checkbox {
public:
    void check() const override {
        std::cout << "Mac Checkbox checked.\n";
    }
};

3️⃣ Abstract Factory

// Abstract Factory for creating UI components
class GUIFactory {
public:
    virtual std::unique_ptr<Button> createButton() const = 0;
    virtual std::unique_ptr<Checkbox> createCheckbox() const = 0;
    virtual ~GUIFactory() = default;
};

5️⃣ Concrete Factories

// Concrete Factory for Windows
class WindowsFactory : public GUIFactory {
public:
    std::unique_ptr<Button> createButton() const override {
        return std::make_unique<WindowsButton>();
    }

    std::unique_ptr<Checkbox> createCheckbox() const override {
        return std::make_unique<WindowsCheckbox>();
    }
};

// Concrete Factory for Mac
class MacFactory : public GUIFactory {
public:
    std::unique_ptr<Button> createButton() const override {
        return std::make_unique<MacButton>();
    }

    std::unique_ptr<Checkbox> createCheckbox() const override {
        return std::make_unique<MacCheckbox>();
    }
};

6️⃣ Client Code

void clientCode(const GUIFactory& factory) {
    auto button = factory.createButton();
    auto checkbox = factory.createCheckbox();
    
    button->click();
    checkbox->check();
}

int main() {
    std::cout << "Using Windows UI:\n";
    WindowsFactory windowsFactory;
    clientCode(windowsFactory);

    std::cout << "\nUsing Mac UI:\n";
    MacFactory macFactory;
    clientCode(macFactory);

    return 0;
}