Abstract Factory Pattern
- Description: Create families of related objects without naming concrete classes — one factory object per family variant; covers classic GoF form, modern template/policy alternatives, and the trade-off vs Factory Method.
- My Notion Note ID: K2C-2-3
- Created: 2026-05-22
- Updated: 2026-05-22
- License: Reuse is very welcome. Please credit Yu Zhang and link back to the original on yuzhang.io
Table of Contents
- 1. Why It Exists
- 2. Structure
- 3. C++ Implementations
- 4. When to Use, When Not To
- 5. Pitfalls
- 6. Related Patterns
- 7. References
1. Why It Exists
- Many systems need multiple related objects produced together that must agree on a variant — e.g. a UI toolkit's
Button+Checkbox+Windowmust all be the macOS variants, or all the Windows variants. Mixing is a bug. - Direct construction (
new MacButton,new MacCheckbox) scatters the variant decision across the codebase. - Abstract Factory bundles the family behind one factory interface → swap the entire variant by swapping the factory.
2. Structure
| Role | Responsibility |
|---|---|
AbstractFactory |
Interface with one method per product type in the family. |
ConcreteFactory |
Implements AbstractFactory for one variant (e.g. Mac, Windows). |
AbstractProduct (one per slot) |
Interface for each kind of product (Button, Checkbox). |
ConcreteProduct |
Variant-specific implementation. |
Client |
Talks only to AbstractFactory + AbstractProduct — never names concretes. |
- Factory object (composition), not factory method (inheritance). Each method on the factory is a Factory Method in the GoF sense.
3. C++ Implementations
3.1 Classic GoF form
#include <memory>
#include <iostream>
struct Button { virtual ~Button() = default; virtual void paint() = 0; };
struct Checkbox { virtual ~Checkbox() = default; virtual void paint() = 0; };
struct MacButton : Button { void paint() override { std::cout << "[Mac Button]\n"; } };
struct MacCheckbox : Checkbox { void paint() override { std::cout << "[Mac Check]\n"; } };
struct WinButton : Button { void paint() override { std::cout << "[Win Button]\n"; } };
struct WinCheckbox : Checkbox { void paint() override { std::cout << "[Win Check]\n"; } };
struct GuiFactory {
virtual ~GuiFactory() = default;
virtual std::unique_ptr<Button> createButton() = 0;
virtual std::unique_ptr<Checkbox> createCheckbox() = 0;
};
struct MacFactory : GuiFactory {
std::unique_ptr<Button> createButton() override { return std::make_unique<MacButton>(); }
std::unique_ptr<Checkbox> createCheckbox() override { return std::make_unique<MacCheckbox>(); }
};
struct WinFactory : GuiFactory {
std::unique_ptr<Button> createButton() override { return std::make_unique<WinButton>(); }
std::unique_ptr<Checkbox> createCheckbox() override { return std::make_unique<WinCheckbox>(); }
};
void buildDialog(GuiFactory& f) {
auto btn = f.createButton();
auto chk = f.createCheckbox();
btn->paint();
chk->paint();
}
- Add a Linux variant → add
LinuxButton,LinuxCheckbox,LinuxFactory. No edits tobuildDialogorGuiFactory. - Add a new product slot (e.g.
Slider) → editGuiFactory+ every concrete factory. The pattern is closed for variants, open for products — adding products is expensive.
3.2 Modern alternative — template policy
template <class ButtonT, class CheckboxT>
struct GuiToolkit {
ButtonT makeButton() const { return {}; }
CheckboxT makeCheckbox() const { return {}; }
};
using MacToolkit = GuiToolkit<MacButton, MacCheckbox>;
using WinToolkit = GuiToolkit<WinButton, WinCheckbox>;
template <class Toolkit>
void buildDialog(const Toolkit& t) {
auto btn = t.makeButton();
auto chk = t.makeCheckbox();
btn.paint();
chk.paint();
}
- Zero virtual dispatch; full inlining. Variant fixed per translation unit / template instantiation — can't pick at runtime.
3.3 Modern alternative — std::variant of factories
#include <variant>
struct MacFactoryV { /* methods returning concrete products */ };
struct WinFactoryV { /* ... */ };
using AnyFactory = std::variant<MacFactoryV, WinFactoryV>;
void buildDialog(AnyFactory& f) {
std::visit([](auto& factory) {
auto btn = factory.createButton();
auto chk = factory.createCheckbox();
btn.paint(); chk.paint();
}, f);
}
- Trades extensibility (closed set) for compile-time exhaustiveness + no heap allocation.
4. When to Use, When Not To
Use when:
- The system must work with multiple variants of a product family, picked at runtime or per-deployment.
- Family-internal consistency matters — e.g. all UI widgets must match the host OS.
- You want a single point of swap for the whole family (config-driven, test doubles).
Avoid when:
- Only one variant exists — overengineered.
- The "family" has one product → use Factory Method instead.
- Products are added often, variants rarely — the pattern punishes that direction (every new product touches every factory).
5. Pitfalls
- Adding a product is O(variants). Each new product method must be implemented on every concrete factory. If product set is volatile, abstract factory becomes a maintenance tax.
- Returning raw pointers → ownership unclear. Always
unique_ptr<Product>from factories. - Hidden coupling to factory choice — clients receive products as base types, but the products may need to interop and rely on being from the same variant. Document the invariant; don't mix factories.
- Stateful factories — factories often look "naturally stateless" but real ones cache or pool. Then they're effectively singletons.
- Confused with Builder — Abstract Factory builds many independent objects of a family. Builder builds one complex object step by step.
6. Related Patterns
- Abstract Factory vs Factory Method — Factory Method = one product chosen via subclass override. Abstract Factory = a family of products chosen via factory object composition. Abstract Factory often uses Factory Method to implement each of its methods.
- Abstract Factory vs Builder — Abstract Factory returns finished products immediately; Builder accumulates state and returns at the end via
build(). - Abstract Factory vs Prototype — Abstract Factory can be implemented as a Prototype registry: factory holds prototypical instances and
clone()s them per call (Prototype Factory). Useful when product configuration is data-driven. - Abstract Factory vs Service Locator — Locator returns any service; Abstract Factory returns one family of related products. Locator more general, less type-safe.
- Abstract Factory vs DI container — A DI container that resolves a whole graph by variant essentially is an abstract factory at scale.
7. References
- Abstract Factory — refactoring.guru
- Abstract Factory — sourcemaking
- GoF, Design Patterns, ch. 3 — Abstract Factory (p. 87)
- cppreference —
std::variant - Alexandrescu, Modern C++ Design, ch. 9 — Abstract Factory via policy templates