The Abstract Factory Pattern and Never Mixing a Light Button With a Dark Label

When a factory needs to build more than one related thing, giving it one method per thing keeps everything it returns from ever clashing.

September 13, 20265 min read14 / 19

The factory method pattern solved one problem: building an object whose exact type you don't know until the moment you need it. But a factory method only ever builds one kind of thing.

Here's the idea in one sentence: when a factory needs to build more than one related thing, give it one method per thing, so a single factory always returns pieces that belong together.

One Factory, Two Things That Have to Match

Picture an app with a light theme and a dark theme. Every screen needs a button and a label, and those two pieces have to agree: a light button on a dark screen, or a dark label on a light screen, just looks broken.

If you built these with a single factory method, you'd have no way to guarantee that. You could easily end up asking for a light button and a dark label by accident, since nothing connects the two calls.

The fix: make one factory responsible for the whole set, not just one piece of it.

Two Factory Methods, One Family

A UIFactory doesn't build one thing. It builds every piece a screen needs, through its own method for each piece.

class Button { public: virtual ~Button() = default; virtual std::string render() const = 0; }; class Label { public: virtual ~Label() = default; virtual std::string render() const = 0; }; class UIFactory { public: virtual ~UIFactory() = default; virtual std::unique_ptr<Button> createButton() = 0; virtual std::unique_ptr<Label> createLabel() = 0; };

Every UIFactory has to be able to build both a button and a label. That's the whole difference from factory method: one method per related thing, instead of just one method.

LightThemeFactory and DarkThemeFactory

Each concrete factory builds its own matching set, and never the other theme's pieces.

class LightButton : public Button { public: std::string render() const override { return "[ Button: dark text on white ]"; } }; class DarkButton : public Button { public: std::string render() const override { return "[ Button: white text on black ]"; } }; class LightLabel : public Label { public: std::string render() const override { return "Label: dark text on white"; } }; class DarkLabel : public Label { public: std::string render() const override { return "Label: white text on black"; } }; class LightThemeFactory : public UIFactory { public: std::unique_ptr<Button> createButton() override { return std::make_unique<LightButton>(); } std::unique_ptr<Label> createLabel() override { return std::make_unique<LightLabel>(); } }; class DarkThemeFactory : public UIFactory { public: std::unique_ptr<Button> createButton() override { return std::make_unique<DarkButton>(); } std::unique_ptr<Label> createLabel() override { return std::make_unique<DarkLabel>(); } };

Notice there's no way to accidentally get a DarkButton out of LightThemeFactory. Each concrete factory only ever knows how to build its own matching set.

Swapping the Whole Family at Once

Code that renders a screen only ever talks to UIFactory. It never picks the theme itself.

void renderScreen(UIFactory& factory) { auto button = factory.createButton(); auto label = factory.createLabel(); std::cout << button->render() << "\n"; std::cout << label->render() << "\n"; } LightThemeFactory light; renderScreen(light); DarkThemeFactory dark; renderScreen(dark);

renderScreen never mentions LightButton, DarkButton, or either theme by name. Swap LightThemeFactory for DarkThemeFactory, and every single piece it builds switches together. There's no way to get a mismatched pair.

A UIFactory with createButton() and createLabel(), where LightThemeFactory and DarkThemeFactory each build their own matching pair ExpandA UIFactory with createButton() and createLabel(), where LightThemeFactory and DarkThemeFactory each build their own matching pair

The next post looks at why this is really just the factory method pattern used twice inside one factory, and a hands-on exercise.

The Essentials

  1. A factory method builds one kind of thing. An abstract factory builds a whole family of related things, through one method per thing.
  2. Every concrete factory builds its own matching set. There's no way to mix pieces from two different concrete factories by accident.
  3. Code using the factory only ever depends on the shared UIFactory shape, so swapping the whole family means swapping one factory instance.