Abstract Factory Pattern
The problem
Your app needs to render a UI with buttons and checkboxes. Simple enough. But then it needs to run on two themes: Light and Dark. Each theme has its own button style and its own checkbox style. If you instantiate LightButton and DarkCheckbox directly in your component code, you scatter theme-aware logic everywhere. Swapping themes at runtime becomes a find-and-replace nightmare.
The deeper issue: the client creating the widgets has to know which concrete classes belong together. That knowledge is fragile. A new theme (High Contrast, say) means hunting down every spot that names a concrete class.
Abstract Factory solves this by grouping the creation of a related family of objects behind a single interface. The client asks the factory for a button and a checkbox. Which concrete factory it holds determines which family it gets. The client never names a concrete class.
Structure
classDiagram class UIFactory { <<interface>> +createButton() Button +createCheckbox() Checkbox }
class LightFactory { +createButton() Button +createCheckbox() Checkbox }
class DarkFactory { +createButton() Button +createCheckbox() Checkbox }
class Button { <<interface>> +render() void +onClick() void }
class Checkbox { <<interface>> +render() void +toggle() void }
class LightButton { +render() void +onClick() void }
class DarkButton { +render() void +onClick() void }
class LightCheckbox { +render() void +toggle() void }
class DarkCheckbox { +render() void +toggle() void }
UIFactory <|.. LightFactory UIFactory <|.. DarkFactory Button <|.. LightButton Button <|.. DarkButton Checkbox <|.. LightCheckbox Checkbox <|.. DarkCheckbox LightFactory ..> LightButton : creates LightFactory ..> LightCheckbox : creates DarkFactory ..> DarkButton : creates DarkFactory ..> DarkCheckbox : createsWhen to use
- Theme or platform families: your system has several coherent product families (Light/Dark, Mac/Windows, mobile/desktop) and the client must use products from one family at a time.
- Consistency enforcement: products within a family must be used together. Mixing a
LightButtonwith aDarkCheckboxwould break visual coherence. - Swappable families at runtime: the factory is injected or selected at startup, letting you swap the entire family without touching client code.
- Isolating third-party dependencies: wrapping third-party UI libraries behind a factory interface lets you swap libraries without rewriting component code.
Prefer a simpler Factory when you only have one product type. Abstract Factory earns its complexity only when you have two or more related product types that must vary together.
Implementation
All three implementations follow the same structure: product interfaces (Button, Checkbox) define the API, two concrete factories (LightFactory, DarkFactory) produce matching product families, and a client function receives a factory and never references a concrete class. Python uses ABCs for both products and the factory. Go uses interfaces throughout and relies on pointer receivers to satisfy them.
Click Run TS to execute. First run downloads Babel (~400 KB, cached after that).
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
Click Run Go to execute. Runs via the Go Playground API.
Tradeoffs
| Pro | Con |
|---|---|
| Guarantees product family consistency — the factory can only produce matching products | Adding a new product type (say, createSlider()) requires changing the abstract factory interface and every concrete factory |
| Client code is fully decoupled from concrete classes; swap factories at the injection point | More classes and interfaces up front; the pattern adds real structural overhead for simple cases |
| New families (a High Contrast theme) are added by implementing the factory interface, without touching existing code | Factories can become large if the product family grows to many types |
| Makes cross-platform or cross-environment support explicit and traceable | Hard to unit-test factories in isolation when products have side effects or heavy dependencies |
Gotchas
- Factory explosion: each new theme or platform multiplies your class count by the number of product types. Keep families small or consider a data-driven approach (config maps to implementations) when families grow past five or six products.
- Interface extension pain: the “Open/Closed” benefit only flows in one direction. Adding a product type to the abstract factory interface is a breaking change for all concrete factories. Plan your product surface area before committing to the pattern.
- Singleton factories: factories are usually stateless. It is tempting to make them singletons, but that can cause problems in tests. Prefer injecting factory instances rather than accessing a global.
- Mixing families by accident: if a client receives a factory but also holds a stray reference to a concrete product from another family, consistency breaks silently. Discipline at the injection boundary matters.
- Overuse: if your app only ever has one theme or one platform target, this pattern is pure overhead. Start with direct instantiation and extract a factory when a second family actually arrives.
References
- Design Patterns: Elements of Reusable Object-Oriented Software — the original GoF book; Abstract Factory is chapter 3.
- Refactoring Guru: Abstract Factory — visual walkthrough with diagrams and multi-language examples.
- SourceMaking: Abstract Factory — concise motivation and structure reference.
Related topics
- Design Patterns — the full GoF catalog this pattern belongs to.
- Factory — the simpler single-product factory; reach for it before adding Abstract Factory complexity.
- Builder — another creational pattern, suited for constructing a single complex object step by step rather than a family of objects.
- Prototype — clones existing objects instead of constructing new ones; useful when construction is expensive.