Abstract Factory

Learning Objectives

Create families of related products without naming concrete classes.

Apply Abstract Factory to hardware or platform abstraction.

Contrast with Factory Method.

Prerequisites: Module 28.

Intuition and Definition

Intent: provide an interface for creating families of related or dependent objects without specifying their concrete classes.

Problem: products must be used *together consistently* — a Windows button with a Windows checkbox, or an STM32 CAN driver with an STM32 timer — and the client must not mix families.

Participants: AbstractFactory (one creation method per product), ConcreteFactory per family, AbstractProduct interfaces, ConcreteProducts, and the Client, which uses only the abstract interfaces.

Why It Matters

Swapping a whole family (simulator vs. real hardware, light vs. dark theme, test vs. production) becomes one line at start-up.

Syntax and Language Notes

In languages with modules and first-class types, a "factory" can simply be an object or dictionary of constructor functions:

const simulator = { can: () => new SimCan(), timer: () => new SimTimer() };
const board = { can: () => new Stm32Can(), timer: () => new Stm32Timer() };

Complete Example

#include <iostream>
#include <memory>
#include <string>

struct CanBus { virtual ~CanBus() = default; virtual void send(int id, int byte) = 0; };
struct Timer  { virtual ~Timer() = default; virtual int nowMs() = 0; };

struct HardwareFactory {                         // abstract factory: one method per product
    virtual ~HardwareFactory() = default;
    virtual std::unique_ptr<CanBus> makeCan() = 0;
    virtual std::unique_ptr<Timer> makeTimer() = 0;
    virtual std::string family() const = 0;
};

// family 1: real board (pretend)
struct Stm32Can : CanBus { void send(int id, int b) override { std::cout << "  [FDCAN1] id=0x" << std::hex << id << std::dec << " data=" << b << "\n"; } };
struct Stm32Timer : Timer { int nowMs() override { return 1000; } };
struct Stm32Factory : HardwareFactory {
    std::unique_ptr<CanBus> makeCan() override { return std::make_unique<Stm32Can>(); }
    std::unique_ptr<Timer> makeTimer() override { return std::make_unique<Stm32Timer>(); }
    std::string family() const override { return "STM32 board"; }
};

// family 2: desktop simulator
struct SimCan : CanBus { void send(int id, int b) override { std::cout << "  [sim bus] " << std::hex << id << std::d
…
STM32 board:
  [FDCAN1] id=0x700 data=100
  [FDCAN1] id=0x700 data=100
simulator:
  [sim bus] 700#1
  [sim bus] 700#2

Step-by-Step Walkthrough

HeartbeatApp gets both products from one factory, so it can never pair an STM32 timer with a simulator bus.

Switching families is a single choice of factory in main.

Adding a new *family* (e.g. a Raspberry Pi board) is one new factory plus products. Adding a new *product kind* (e.g. Gpio) changes every factory — the pattern's main cost.

classDiagram
  class HardwareFactory {
    <<interface>>
    +makeCan() CanBus
    +makeTimer() Timer
  }
  HardwareFactory <|.. Stm32Factory
  HardwareFactory <|.. SimFactory
  CanBus <|.. Stm32Can
  CanBus <|.. SimCan
  Timer <|.. Stm32Timer
  Timer <|.. SimTimer
  HeartbeatApp ..> HardwareFactory

Common Mistakes

Using Abstract Factory for a single product — that's a Factory Method or plain factory.

Leaking concrete types (dynamic_cast<Stm32Can*>) in the client, defeating the pattern.

Huge factories with dozens of methods; split by subsystem.

Trade-offs and Alternatives

New families are easy; new products are expensive. Alternatives: dependency injection of each product separately (more flexible, less family-safety), or compile-time selection with templates (HeartbeatApp<Stm32Hal>) when the family is fixed per build.

Real-World Use

Cross-platform UI toolkits (widgets per OS), database access libraries (connection + command + parameter per database vendor), and hardware abstraction layers that switch between board and simulator builds.

Practice

Q1. (Easy) What guarantee does Abstract Factory give the client?

Answer: That all products it uses come from the same family.

Q2. (Medium) What is expensive to add, and why?

Answer: A new product type: every concrete factory must implement a new creation method.

Q3. (Advanced) How could the same family selection be done at compile time with zero virtual calls?

Answer: Make the client a template over a "HAL" type that provides nested types or static functions (template <class Hal> class HeartbeatApp { typename Hal::Can can_; ... }) and instantiate HeartbeatApp<Stm32Hal> or HeartbeatApp<SimHal> per build — a policy-based design (Module 58).

Interview Question

Q1. "Abstract Factory vs Factory Method?" Answer: Factory Method is *one* overridable method creating *one* product, varied by subclassing the creator. Abstract Factory is an *object* with several creation methods for a *family* of products, passed to the client (composition). Abstract Factories are often implemented *with* factory methods. Choose Abstract Factory when products must be consistent with each other.

Quick Quiz

Q1. Does the client know concrete product classes?

Answer: No, only the abstract interfaces.

Q2. What is cheap to add: a family or a product kind?

Answer: A family.

Q3. A typical embedded use?

Answer: Switching between hardware and simulator HALs.

Summary

Abstract Factory creates consistent families of products behind interfaces.

Families are cheap to add, product kinds are not.

Related: Modules 28, 34 (Bridge), 72 (ports and adapters).