Design Patterns, Explained Simply · Creational Patterns: Creating Objects Flexibly
Abstract Factory
Create whole families of related objects that must match, without the code that uses them knowing which family it got.
The problem
A UI kit supports a light theme and a dark theme. Each screen builds buttons, checkboxes and text fields. Somewhere, a developer creates a DarkButton next to a LightCheckbox, and the settings page ships with a white checkbox on a black panel.
Every screen is full of if theme == "dark" checks, and adding a "high contrast" theme means finding every one of them.
Real-world analogy
A furniture showroom sells matching sets: a Victorian chair, sofa and table, or a modern chair, sofa and table. You pick a style catalogue, and everything you order from it matches. You never accidentally get a Victorian chair with a modern table.
Without the pattern
def settings_page(theme):
button = DarkButton("Save") if theme == "dark" else LightButton("Save")
checkbox = LightCheckbox("Notifications") # oops: forgot the theme check here
return [button, checkbox]
The pattern
- Abstract factory (
Theme): an interface with one creation method per product kind:button(),checkbox(). - Concrete factories (
LightTheme,DarkTheme): each creates products from one family. - Abstract products (
Button,Checkbox) and concrete products (LightButton,DarkCheckbox, ...). - Client (
settings_page): receives one factory and creates everything through it, so the family is always consistent.
The code
class LightButton:
def __init__(self, label):
self.label = label
def render(self):
return f"[ {self.label} ] (black text on white)"
class LightCheckbox:
def __init__(self, label):
self.label = label
def render(self):
return f"[x] {self.label} (grey box)"
class DarkButton:
def __init__(self, label):
self.label = label
def render(self):
return f"[ {self.label} ] (white text on charcoal)"
class DarkCheckbox:
def __init__(self, label):
self.label = label
def render(self):
return f"[x] {self.label} (outlined box)"
class LightTheme: # concrete factory: one family
def button(self, label):
return LightButton(label)
def checkbox(self, label):
return LightCheckbox(label)
class DarkTheme:
def button(self, label):
return DarkButton(label)
def checkbox(self, label):
return DarkCheckbox(label)
def settings_page(theme): # client: never names a concrete widget class
widgets = [theme.checkbox("Email me updates"), theme.button("Save")]
return [w.render() for w in widgets]
for theme in [LightTheme(), DarkTheme()]:
print(f"{type(theme).__name__}:")
for line in settings_page(theme):
print(" " + line)
Output
LightTheme: [x] Email me updates (grey box) [ Save ] (black text on white) DarkTheme: [x] Email me updates (outlined box) [ Save ] (white text on charcoal)
interface Widget { render(): string; }
class LightButton implements Widget {
constructor(private label: string) {}
render() { return `[ ${this.label} ] (black text on white)`; }
}
class LightCheckbox implements Widget {
constructor(private label: string) {}
render() { return `[x] ${this.label} (grey box)`; }
}
class DarkButton implements Widget {
constructor(private label: string) {}
render() { return `[ ${this.label} ] (white text on charcoal)`; }
}
class DarkCheckbox implements Widget {
constructor(private label: string) {}
render() { return `[x] ${this.label} (outlined box)`; }
}
interface Theme { // the abstract factory
button(label: string): Widget;
checkbox(label: string): Widget;
}
class LightTheme implements Theme { // concrete factory: one family
button(label: string) { return new LightButton(label); }
checkbox(label: string) { return new LightCheckbox(label); }
}
class DarkTheme implements Theme {
button(label: string) { return new DarkButton(label); }
checkbox(label: string) { return new DarkCheckbox(label); }
}
function settingsPage(theme: Theme): string[] { // client: never names a concrete widget class
const widgets = [theme.checkbox("Email me updates"), theme.button("Save")];
return widgets.map((w) => w.render());
}
for (const theme of [new LightTheme(), new DarkTheme()]) {
console.log(`${theme.constructor.name}:`);
for (const line of settingsPage(theme)) console.log(" " + line);
}
Output
LightTheme: [x] Email me updates (grey box) [ Save ] (black text on white) DarkTheme: [x] Email me updates (outlined box) [ Save ] (white text on charcoal)
import java.util.List;
public class Main {
static List<String> settingsPage(Theme theme) { // client: never names a concrete widget class
List<Widget> widgets = List.of(theme.checkbox("Email me updates"), theme.button("Save"));
return widgets.stream().map(Widget::render).toList();
}
public static void main(String[] args) {
for (Theme theme : List.of(new LightTheme(), new DarkTheme())) {
System.out.println(theme.getClass().getSimpleName() + ":");
for (String line : settingsPage(theme)) System.out.println(" " + line);
}
}
}
interface Widget { String render(); }
record LightButton(String label) implements Widget {
public String render() { return "[ " + label + " ] (black text on white)"; }
}
record LightCheckbox(String label) implements Widget {
public String render() { return "[x] " + label + " (grey box)"; }
}
record DarkButton(String label) implements Widget {
public String render() { return "[ " + label + " ] (white text on charcoal)"; }
}
record DarkCheckbox(String label) implements Widget {
public String render() { return "[x] " + label + " (outlined box)"; }
}
interface Theme { // the abstract factory
Widget button(String label);
Widget checkbox(String label);
}
class LightTheme implements Theme { // concrete factory: one family
public Widget button(String label) { return new LightButton(label); }
public Widget checkbox(String label) { return new LightCheckbox(label); }
}
class DarkTheme implements Theme {
public Widget button(String label) { return new DarkButton(label); }
public Widget checkbox(String label) { return new DarkCheckbox(label); }
}
Output
LightTheme: [x] Email me updates (grey box) [ Save ] (black text on white) DarkTheme: [x] Email me updates (outlined box) [ Save ] (white text on charcoal)
Run this on your computer — see “Get Started” for setup.
<?php
interface Widget { public function render(): string; }
class LightButton implements Widget {
public function __construct(private string $label) {}
public function render(): string { return "[ {$this->label} ] (black text on white)"; }
}
class LightCheckbox implements Widget {
public function __construct(private string $label) {}
public function render(): string { return "[x] {$this->label} (grey box)"; }
}
class DarkButton implements Widget {
public function __construct(private string $label) {}
public function render(): string { return "[ {$this->label} ] (white text on charcoal)"; }
}
class DarkCheckbox implements Widget {
public function __construct(private string $label) {}
public function render(): string { return "[x] {$this->label} (outlined box)"; }
}
interface Theme { // the abstract factory
public function button(string $label): Widget;
public function checkbox(string $label): Widget;
}
class LightTheme implements Theme { // concrete factory: one family
public function button(string $label): Widget { return new LightButton($label); }
public function checkbox(string $label): Widget { return new LightCheckbox($label); }
}
class DarkTheme implements Theme {
public function button(string $label): Widget { return new DarkButton($label); }
public function checkbox(string $label): Widget { return new DarkCheckbox($label); }
}
function settingsPage(Theme $theme): array { // client: never names a concrete widget class
$widgets = [$theme->checkbox("Email me updates"), $theme->button("Save")];
return array_map(fn(Widget $w) => $w->render(), $widgets);
}
foreach ([new LightTheme(), new DarkTheme()] as $theme) {
echo get_class($theme), ":\n";
foreach (settingsPage($theme) as $line) echo " $line\n";
}
Output
LightTheme: [x] Email me updates (grey box) [ Save ] (black text on white) DarkTheme: [x] Email me updates (outlined box) [ Save ] (white text on charcoal)
The theme is chosen once, at startup, and every widget on every page matches. A high-contrast theme is one new factory class plus its widgets; no page changes.
When to use it
- You create several related objects that must be used together: UI widgets per theme or platform, database connection + command + transaction per vendor, cloud storage + queue + secrets per provider.
- You want to switch the whole family in one place (config, environment, tests).
When not to use it
- You only create one kind of object. That is a Factory Method or simple factory.
- The families rarely change and there are only two. The extra interfaces may cost more than an occasional
if. - Adding a new product kind (say, a slider) means changing the factory interface and every factory. If product kinds change often, the pattern hurts.
You've already used it
- Java:
DocumentBuilderFactoryand JDBC drivers; a driver supplies matchingConnection,StatementandResultSetimplementations. - Python: DB-API drivers (
sqlite3,psycopg) supply matching connection and cursor types. - Cross-platform UI toolkits: one factory per operating system creates native-looking widgets.
Key points
- Abstract Factory creates families of related objects that must match.
- The client gets one factory and never names concrete product classes.
- Switching families is a one-line change; adding a new product kind is expensive.
- Factory Method creates one product via subclassing; Abstract Factory is an object with several creation methods.
- Further reading: Refactoring Guru: Abstract Factory.
Practice questions
When would you choose Abstract Factory over Factory Method?
Answer
When you need to create several related objects that must match (a family), such as a button, a checkbox and a dialog for one theme. Factory Method creates a single product through an overridable method; Abstract Factory is an object with one creation method per product kind.
What is the main drawback of Abstract Factory?
Answer
Adding a new kind of product (say, a slider) means changing the abstract factory interface and every concrete factory. It is easy to add new families, hard to add new product kinds.
Give a real-world example of Abstract Factory.
Answer
Database drivers: a JDBC or DB-API driver supplies matching connection, statement/cursor and result types for one database vendor. UI toolkits that create native widgets per operating system are another example.
Open in the app to mark lessons complete, take notes and track your progress.