1.2 Abstract Factory (Creational) Design Pattern

The Abstract Factory Design Pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. 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.

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.

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.

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.

2 Comparison with the Factory Pattern

  1. Layer of Abstraction:
    1. 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:
    1. 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:
    1. 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:
    1. 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.

3 Components of Abstract Factory Pattern

  1. Abstract Factory:
    1. The Abstract Factory serves as the backbone of the pattern, defining a high-level blueprint for creating families of related objects. It declares a series of abstract methods, each responsible for creating a specific type of object within a family. By defining these methods at a high level, it abstracts away the details of object creation and ensures consistency across different families of objects.
  2. Concrete Factories:
    1. Concrete Factories implement the rules specified by the abstract factory. Each concrete factory contains the logic for creating specific instances of objects within a particular family. Multiple concrete factories can exist, each tailored to produce a distinct family of related objects. These factories adhere to the common interface defined by the abstract factory, ensuring that the client can seamlessly switch between different families of objects.
  3. Abstract Products:
    1. Abstract Products represent the common interface for a family of related objects. They define a set of abstract methods or properties that all concrete products within a family must implement. By defining this interface at an abstract level, it allows for the creation of interchangeable objects within a family. Abstract products act as blueprints for concrete products and facilitate the seamless interchangeability of objects within a family.
  4. Concrete Products:
    1. Concrete Products are the actual instances of objects created by concrete factories. They implement the methods declared in the abstract products, ensuring consistency within a family of objects. Each concrete product belongs to a specific category or family of related objects and provides the concrete implementation of the abstract methods defined by the abstract product. Concrete products encapsulate the specific behavior and characteristics of individual objects within a family.
  5. Client:
    1. The Client utilizes the abstract factory to create families of objects without specifying their concrete types. It interacts with objects through the abstract interfaces provided by abstract products, allowing for loose coupling between the client and the concrete implementations. The client enjoys the flexibility of seamlessly switching between families of objects by changing the concrete factory instance, without needing to modify its code. This promotes flexibility, scalability, and maintainability in the client codebase.

Implementation

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

                   +--------------------------+
                   |      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;
}