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#2Step-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 ..> HardwareFactoryCommon 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).