Design Patterns (GoF and modern) Module 2 – Creational patterns: Factory Method to Singleton
Abstract Factory: families of related objects
Abstract Factory creates families of related objects that must not be mixed, in six languages, with the cost of a new product type and when DI is simpler.
What you will learn
- Implement an Abstract Factory that keeps products from one family together
- Explain the trade-off that a new family is cheap and a new product type touches every factory
- Replace a hand-written Abstract Factory with dependency injection where that is simpler
- Recognise Abstract Factory in standard APIs such as javax.xml.parsers.DocumentBuilderFactory
Before you start
What you’ll learn · Before you start
What you will learn
- Implement an Abstract Factory that keeps products from one family together
- Explain the trade-off that a new family is cheap and a new product type touches every factory
- Replace a hand-written Abstract Factory with dependency injection where that is simpler
- Recognise Abstract Factory in standard APIs such as javax.xml.parsers.DocumentBuilderFactory
Before you start
On this page
Abstract Factory is a creational design pattern for creating families of related objects without naming their concrete classes. One interface declares a creation method for each product type of the family: a badge, a table and a button of a console theme, say, or the orders and stock repositories of a storage back-end. Each family implements the whole interface with products that belong together. The client is handed a single factory object and asks it for everything it needs, so every object it gets comes from the same family and none can be mixed in by mistake. Reach for it when several product types must vary together and stay consistent: visual themes, storage engines, platforms, or a production family and a test family of the same parts.
This lesson builds two families of console widgets and two families of storage in Python, translates the widgets into Java, TypeScript, Go, C++ and Rust, counts what each kind of change costs, and finds the pattern in the JDK’s XML API. It ends with the case where you do not need a factory class at all.
The problem: parts that must match
Suppose a console program can draw its screens in plain ASCII or with Unicode box-drawing characters. Written
without a pattern, every function that draws something asks if theme == "box" and picks a style. The check is
repeated in the badge code, the table code and the button code, and the day someone forgets it in one place, a
screen comes out with a box-drawn table and an ASCII button. Nothing fails; it just looks broken.
Storage has the same shape with higher stakes. An order service needs an orders repository and a stock repository. In production both talk to the same database inside one transaction; in tests both are in memory. A test that combines a real repository with a fake one does not look broken at all. It passes, and it tests something that will never run.
The common thread: the parts are chosen together, and a wrong combination is the bug. Factory Method chooses one product; Abstract Factory chooses a whole family at once.
The parts, as a grid
Product types by family
Text description of the diagram
The diagram is a grid with a row for each product type and a column for each family.
- The rows are the three creation methods of WidgetFactory: badge(), table() and button().
- The columns are the two families, AsciiWidgets and BoxWidgets.
- Each cell is the concrete product that one family makes for one product type. AsciiWidgets makes a badge in round brackets, a table with plus, minus and bar borders, and a button in square brackets. BoxWidgets makes a badge in guillemets, a table with box-drawing borders, and a button between arrowheads.
Two notes under the grid say what each kind of change costs. A new family adds a column: one new factory and its products, with nothing edited. A new product type adds a row: one new method in the interface, and a new method in every existing factory.
- The abstract factory,
WidgetFactory, declares one creation method per product type:badge(),table()andbutton(). - Each concrete factory is one family:
AsciiWidgetsandBoxWidgets. - The products are what the methods return. In the classic catalogue each product type has an interface of its
own (a
Button, aBadge); in this short example they are lines of text, which keeps the focus on the factory. - The client,
order_screen(), takes aWidgetFactoryand never names a family.
Each cell of the grid is one concrete product. Read the grid by columns and you see families, by rows and you see product types. That picture is the key to the pattern’s main trade-off, which comes right after the code.
Abstract Factory in Python
"""Abstract Factory: one factory per family of console widgets; a screen built through one
factory never mixes them."""
from abc import ABC, abstractmethod
class WidgetFactory(ABC):
"""One creation method per product type. Each family implements all of them."""
@abstractmethod
def badge(self, text: str) -> str: ...
@abstractmethod
def table(self, header: list[str], rows: list[list[str]]) -> list[str]: ...
@abstractmethod
def button(self, label: str) -> str: ...
def widths_of(header, rows):
return [max(len(str(row[i])) for row in [header, *rows]) for i in range(len(header))]
def cells(row, widths, bar):
"""One table row between bars: the first column left-aligned, the others right-aligned."""
texts = [
f" {str(v):<{w}} " if i == 0 else f" {str(v):>{w}} "
for i, (v, w) in enumerate(zip(row, widths))
]
return bar + bar.join(texts) + bar
class AsciiWidgets(WidgetFactory):
def badge(self, text):
return f"({text})"
def table(self, header, rows):
widths = widths_of(header, rows)
line = "+" + "+".join("-" * (w + 2) for w in widths) + "+"
body = [cells(r, widths, "|") for r in rows]
return [line, cells(header, widths, "|"), line, *body, line]
def button(self, label):
return f"[ {label} ]"
class BoxWidgets(WidgetFactory):
def badge(self, text):
return f"«{text}»"
def table(self, header, rows):
widths = widths_of(header, rows)
def rule(left, middle, right):
return left + middle.join("─" * (w + 2) for w in widths) + right
body = [cells(r, widths, "│") for r in rows]
head = [rule("┌", "┬", "┐"), cells(header, widths, "│"), rule("├", "┼", "┤")]
return [*head, *body, rule("└", "┴", "┘")]
def button(self, label):
return f"▶ {label} ◀"
def order_screen(widgets: WidgetFactory) -> list[str]:
"""The client: it asks the factory for every widget and never names a family."""
return [
f"Order 1042 {widgets.badge('shipped')}",
*widgets.table(["item", "qty"], [["kettle", 1], ["mug", 2]]),
widgets.button("Track parcel"),
]
for family in (AsciiWidgets(), BoxWidgets()):
print(f"--- {type(family).__name__}")
print("\n".join(order_screen(family)))
# Without the factory, nothing stops a screen from taking its table from one family and its
# button from another.
print("--- mixed by hand")
mixed = BoxWidgets().table(["item", "qty"], [["kettle", 1]])
mixed.append(AsciiWidgets().button("Track parcel"))
print("\n".join(mixed))
# A family that forgets a product type cannot even be created.
class HalfWidgets(WidgetFactory):
def table(self, header, rows):
return []
def button(self, label):
return label
try:
HalfWidgets()
except TypeError as error:
print("TypeError:", error) Output
--- AsciiWidgets Order 1042 (shipped) +--------+-----+ | item | qty | +--------+-----+ | kettle | 1 | | mug | 2 | +--------+-----+ [ Track parcel ] --- BoxWidgets Order 1042 «shipped» ┌────────┬─────┐ │ item │ qty │ ├────────┼─────┤ │ kettle │ 1 │ │ mug │ 2 │ └────────┴─────┘ ▶ Track parcel ◀ --- mixed by hand ┌────────┬─────┐ │ item │ qty │ ├────────┼─────┤ │ kettle │ 1 │ └────────┴─────┘ [ Track parcel ] TypeError: Can't instantiate abstract class HalfWidgets without an implementation for abstract method 'badge'
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 themes.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The same order_screen() draws two consistent screens because it receives a different factory each time. The third
block shows what the pattern prevents: build a screen from two families by hand and you get a box-drawn table with an
ASCII button. The last line shows the safety net Python adds. WidgetFactory is an abstract base class, and
HalfWidgets forgot badge(), so Python refuses to create it and names the missing method. A family has to make
every product type before it can be used at all.
What each kind of change costs
With F families and P product types there are F × P concrete products and F concrete factories: 2 × 3 = 6 products and 2 factories in the example. The two kinds of change cost very different amounts:
- A new family is a new column: one factory class and its P products. No existing class is edited, so a third theme, or a family written by someone else in a plug-in, slots in without touching the others.
- A new product type is a new row: one new method in the abstract factory and a new method, with a new product, in every one of the F factories. With two themes that is two edits; with twenty themes from twenty plug-in authors, it is twenty edits in code you may not own.
This is the expression problem, named by Philip Wadler in 1998: you can make adding cases easy or adding operations easy, and the usual designs pick one. An Abstract Factory makes families (cases) cheap and product types (operations) expensive. If you expect new product types more often than new families, the pattern points the wrong way.
A common escape hatch is a default method in the abstract factory, so old families inherit some version of the new product. It saves the edits and gives up the guarantee: an old box-drawing family would then draw the default, probably ASCII, tooltip, which is exactly the mixing the pattern exists to stop. The exercise at the end has you make both kinds of change and feel the difference.
A second example: storage for production and for tests
storage/main.py
"""The same service and the same orders on each store: the two real families agree, the mixed
one does not."""
from service import OrderService
from stores import MemoryStore, MixedStore, SqliteStore
STOCK = {"kettle": 3, "mug": 5}
WANTED = [("kettle", 2), ("mug", 4), ("kettle", 2)]
for store in (SqliteStore(STOCK), MemoryStore(STOCK), MixedStore(STOCK)):
service = OrderService(store)
print(f"{type(store).__name__}: {'; '.join(service.try_orders(WANTED))}")
print(f" orders {service.orders.all()}")
print(f" stock {service.stock.levels()}") Output
SqliteStore: order 1; order 2; refused: only 1 kettle left
orders [(1, 'kettle', 2), (2, 'mug', 4)]
stock {'kettle': 1, 'mug': 1}
MemoryStore: order 1; order 2; refused: only 1 kettle left
orders [(1, 'kettle', 2), (2, 'mug', 4)]
stock {'kettle': 1, 'mug': 1}
MixedStore: order 1; order 2; refused: only 1 kettle left
orders [(1, 'kettle', 2), (2, 'mug', 4), (3, 'kettle', 2)]
stock {'kettle': 1, 'mug': 1}
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 main.py
storage/service.py
"""The service: it gets one store (a factory of repositories) and never names SQLite or memory."""
from stores import OutOfStock
class OrderService:
def __init__(self, store):
self.store = store
self.orders, self.stock = store.orders(), store.stock()
def place(self, sku, qty):
"""Saves the order and takes the stock, both or neither."""
with self.store.transaction():
order_id = self.orders.add(sku, qty)
self.stock.take(sku, qty) # raises OutOfStock, which must undo the order too
return order_id
def try_orders(self, wanted):
results = []
for sku, qty in wanted:
try:
results.append(f"order {self.place(sku, qty)}")
except OutOfStock as refusal:
results.append(f"refused: {refusal}")
return results storage/stores.py
"""Two storage families for one service. Each family makes an orders repository and a stock
repository that share one transaction; MixedStore takes its parts from both families to show
what that breaks."""
import sqlite3
from contextlib import contextmanager
class OutOfStock(Exception):
pass
# ---- the SQLite family: one connection, so one transaction covers both repositories ----------
class SqliteOrders:
def __init__(self, db):
self.db = db
def add(self, sku, qty):
return self.db.execute("INSERT INTO orders (sku, qty) VALUES (?, ?)", (sku, qty)).lastrowid
def all(self):
return self.db.execute("SELECT id, sku, qty FROM orders ORDER BY id").fetchall()
class SqliteStock:
def __init__(self, db):
self.db = db
def take(self, sku, qty):
row = self.db.execute("SELECT available FROM stock WHERE sku = ?", (sku,)).fetchone()
(available,) = row
if available < qty:
raise OutOfStock(f"only {available} {sku} left")
self.db.execute("UPDATE stock SET available = available - ? WHERE sku = ?", (qty, sku))
def levels(self):
return dict(self.db.execute("SELECT sku, available FROM stock ORDER BY sku"))
class SqliteStore:
def __init__(self, stock):
self.db = sqlite3.connect(":memory:")
self.db.execute(
"CREATE TABLE orders (id INTEGER PRIMARY KEY, sku TEXT NOT NULL, qty INTEGER NOT NULL)"
)
self.db.execute("CREATE TABLE stock (sku TEXT PRIMARY KEY, available INTEGER NOT NULL)")
self.db.executemany("INSERT INTO stock VALUES (?, ?)", stock.items())
self.db.commit()
def orders(self):
return SqliteOrders(self.db)
def stock(self):
return SqliteStock(self.db)
@contextmanager
def transaction(self):
with self.db: # commits when the block ends normally, rolls back when it raises
yield
# ---- the in-memory family, for tests: lists, dictionaries and a transaction that restores them ---
class MemoryOrders:
def __init__(self, rows):
self.rows = rows
def add(self, sku, qty):
self.rows.append((len(self.rows) + 1, sku, qty))
return len(self.rows)
def all(self):
return list(self.rows)
class MemoryStock:
def __init__(self, available):
self.available = available
def take(self, sku, qty):
if self.available[sku] < qty:
raise OutOfStock(f"only {self.available[sku]} {sku} left")
self.available[sku] -= qty
def levels(self):
return dict(sorted(self.available.items()))
class MemoryStore:
def __init__(self, stock):
self.rows, self.available = [], dict(stock)
def orders(self):
return MemoryOrders(self.rows)
def stock(self):
return MemoryStock(self.available)
@contextmanager
def transaction(self):
saved_rows, saved_stock = list(self.rows), dict(self.available)
try:
yield
except BaseException:
self.rows[:] = saved_rows
self.available.clear()
self.available.update(saved_stock)
raise
# ---- not a family: orders from memory, stock and the transaction from SQLite -----------------
class MixedStore:
def __init__(self, stock):
self.sqlite, self.memory = SqliteStore(stock), MemoryStore(stock)
def orders(self):
return self.memory.orders()
def stock(self):
return self.sqlite.stock()
def transaction(self):
return self.sqlite.transaction() Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
OrderService.place() saves an order and then takes the stock, inside one transaction: both happen or neither does.
The third order asks for 2 kettles when 1 is left, so taking the stock raises OutOfStock and the transaction must
also undo the order it has just saved.
- The SQLite family gives both repositories the same connection. A
sqlite3connection used as a context manager commits when the block ends normally and rolls back when it raises, so one rollback undoes both changes. - The in-memory family uses a list and a dictionary, and its
transaction()restores both from copies when the block raises. It needs no disk and no database, which is what makes it fast in tests. MixedStoretakes its orders from the memory family and its stock and transaction from SQLite. The rollback belongs to SQLite and cannot touch a Python list, so the refused order stays saved: a third, ghost order that no stock was taken for.
The two real families print the same results for the same orders, which is the property that makes the fast family
a fair stand-in for the real one. Run the same checks against both, as main.py does, and a fake that drifts from the
real behaviour shows up as a difference.
One design in six languages
The widget families in Java, TypeScript, Go, C++ and Rust. Each program prints the two screens that themes.py
prints first.
Java · compiled/Themes.java
// Abstract Factory in Java 25: an interface with one creation method per product type, one
// class per family.
// Run: java Themes.java
import java.util.ArrayList;
import java.util.List;
public class Themes {
interface WidgetFactory {
String badge(String text);
List<String> table(List<String> header, List<List<String>> rows);
String button(String label);
}
static int[] widthsOf(List<String> header, List<List<String>> rows) {
int[] widths = new int[header.size()];
List<List<String>> all = new ArrayList<>(rows);
all.addFirst(header);
for (List<String> row : all) {
for (int i = 0; i < row.size(); i++) {
widths[i] = Math.max(widths[i], row.get(i).length());
}
}
return widths;
}
// One table row between bars: the first column left-aligned, the others right-aligned.
static String cells(List<String> row, int[] widths, String bar) {
List<String> texts = new ArrayList<>();
for (int i = 0; i < row.size(); i++) {
String format = i == 0 ? " %-" + widths[i] + "s " : " %" + widths[i] + "s ";
texts.add(String.format(format, row.get(i)));
}
return bar + String.join(bar, texts) + bar;
}
static String rule(int[] widths, String left, String fill, String middle, String right) {
List<String> parts = new ArrayList<>();
for (int w : widths) parts.add(fill.repeat(w + 2));
return left + String.join(middle, parts) + right;
}
static final class AsciiWidgets implements WidgetFactory {
public String badge(String text) {
return "(" + text + ")";
}
public List<String> table(List<String> header, List<List<String>> rows) {
int[] widths = widthsOf(header, rows);
String line = rule(widths, "+", "-", "+", "+");
List<String> out = new ArrayList<>(List.of(line, cells(header, widths, "|"), line));
for (List<String> row : rows) out.add(cells(row, widths, "|"));
out.add(line);
return out;
}
public String button(String label) {
return "[ " + label + " ]";
}
}
static final class BoxWidgets implements WidgetFactory {
public String badge(String text) {
return "«" + text + "»";
}
public List<String> table(List<String> header, List<List<String>> rows) {
int[] widths = widthsOf(header, rows);
List<String> out = new ArrayList<>(List.of(
rule(widths, "┌", "─", "┬", "┐"),
cells(header, widths, "│"),
rule(widths, "├", "─", "┼", "┤")));
for (List<String> row : rows) out.add(cells(row, widths, "│"));
out.add(rule(widths, "└", "─", "┴", "┘"));
return out;
}
public String button(String label) {
return "▶ " + label + " ◀";
}
}
// The client: it asks the factory for every widget and never names a family.
static List<String> orderScreen(WidgetFactory widgets) {
List<String> screen = new ArrayList<>();
screen.add("Order 1042 " + widgets.badge("shipped"));
List<List<String>> rows = List.of(List.of("kettle", "1"), List.of("mug", "2"));
screen.addAll(widgets.table(List.of("item", "qty"), rows));
screen.add(widgets.button("Track parcel"));
return screen;
}
public static void main(String[] args) {
for (WidgetFactory family : List.of(new AsciiWidgets(), new BoxWidgets())) {
System.out.println("--- " + family.getClass().getSimpleName());
orderScreen(family).forEach(System.out::println);
}
}
} TypeScript · compiled/themes.ts
// Abstract Factory in TypeScript: an interface with one creation method per product type, one
// class per family.
// Run: node themes.ts
interface WidgetFactory {
badge(text: string): string;
table(header: string[], rows: string[][]): string[];
button(label: string): string;
}
function widthsOf(header: string[], rows: string[][]): number[] {
return header.map((_, i) => Math.max(...[header, ...rows].map((row) => row[i]!.length)));
}
// One table row between bars: the first column left-aligned, the others right-aligned.
function cells(row: string[], widths: number[], bar: string): string {
return (
bar +
row
.map((v, i) => ` ${i === 0 ? v.padEnd(widths[i]!) : v.padStart(widths[i]!)} `)
.join(bar) +
bar
);
}
function rule(
widths: number[],
left: string,
fill: string,
middle: string,
right: string,
): string {
return left + widths.map((w) => fill.repeat(w + 2)).join(middle) + right;
}
class AsciiWidgets implements WidgetFactory {
badge(text: string): string {
return `(${text})`;
}
table(header: string[], rows: string[][]): string[] {
const widths = widthsOf(header, rows);
const line = rule(widths, "+", "-", "+", "+");
return [
line,
cells(header, widths, "|"),
line,
...rows.map((r) => cells(r, widths, "|")),
line,
];
}
button(label: string): string {
return `[ ${label} ]`;
}
}
class BoxWidgets implements WidgetFactory {
badge(text: string): string {
return `«${text}»`;
}
table(header: string[], rows: string[][]): string[] {
const widths = widthsOf(header, rows);
return [
rule(widths, "┌", "─", "┬", "┐"),
cells(header, widths, "│"),
rule(widths, "├", "─", "┼", "┤"),
...rows.map((r) => cells(r, widths, "│")),
rule(widths, "└", "─", "┴", "┘"),
];
}
button(label: string): string {
return `▶ ${label} ◀`;
}
}
// The client: it asks the factory for every widget and never names a family.
function orderScreen(widgets: WidgetFactory): string[] {
return [
`Order 1042 ${widgets.badge("shipped")}`,
...widgets.table(
["item", "qty"],
[
["kettle", "1"],
["mug", "2"],
],
),
widgets.button("Track parcel"),
];
}
for (const family of [new AsciiWidgets(), new BoxWidgets()]) {
console.log(`--- ${family.constructor.name}`);
console.log(orderScreen(family).join("\n"));
} Go · compiled/themes.go
// Abstract Factory in Go: an interface with one creation method per product type, one struct
// per family. A type that misses a method does not satisfy the interface, so the compiler
// rejects it wherever it is used as one.
// Run: go run themes.go
package main
import (
"fmt"
"strings"
"unicode/utf8"
)
type WidgetFactory interface {
Badge(text string) string
Table(header []string, rows [][]string) []string
Button(label string) string
}
func widthsOf(header []string, rows [][]string) []int {
widths := make([]int, len(header))
for _, row := range append([][]string{header}, rows...) {
for i, v := range row {
widths[i] = max(widths[i], utf8.RuneCountInString(v))
}
}
return widths
}
// cells is one table row between bars: the first column left-aligned, the others right-aligned.
func cells(row []string, widths []int, bar string) string {
texts := make([]string, len(row))
for i, v := range row {
if i == 0 {
texts[i] = fmt.Sprintf(" %-*s ", widths[i], v)
} else {
texts[i] = fmt.Sprintf(" %*s ", widths[i], v)
}
}
return bar + strings.Join(texts, bar) + bar
}
func rule(widths []int, left, fill, middle, right string) string {
parts := make([]string, len(widths))
for i, w := range widths {
parts[i] = strings.Repeat(fill, w+2)
}
return left + strings.Join(parts, middle) + right
}
type AsciiWidgets struct{}
func (AsciiWidgets) Badge(text string) string { return "(" + text + ")" }
func (AsciiWidgets) Table(header []string, rows [][]string) []string {
widths := widthsOf(header, rows)
line := rule(widths, "+", "-", "+", "+")
out := []string{line, cells(header, widths, "|"), line}
for _, row := range rows {
out = append(out, cells(row, widths, "|"))
}
return append(out, line)
}
func (AsciiWidgets) Button(label string) string { return "[ " + label + " ]" }
type BoxWidgets struct{}
func (BoxWidgets) Badge(text string) string { return "«" + text + "»" }
func (BoxWidgets) Table(header []string, rows [][]string) []string {
widths := widthsOf(header, rows)
out := []string{
rule(widths, "┌", "─", "┬", "┐"),
cells(header, widths, "│"),
rule(widths, "├", "─", "┼", "┤"),
}
for _, row := range rows {
out = append(out, cells(row, widths, "│"))
}
return append(out, rule(widths, "└", "─", "┴", "┘"))
}
func (BoxWidgets) Button(label string) string { return "▶ " + label + " ◀" }
// orderScreen is the client: it asks the factory for every widget and never names a family.
func orderScreen(widgets WidgetFactory) []string {
screen := []string{"Order 1042 " + widgets.Badge("shipped")}
rows := [][]string{{"kettle", "1"}, {"mug", "2"}}
screen = append(screen, widgets.Table([]string{"item", "qty"}, rows)...)
return append(screen, widgets.Button("Track parcel"))
}
func main() {
families := []struct {
name string
widgets WidgetFactory
}{{"AsciiWidgets", AsciiWidgets{}}, {"BoxWidgets", BoxWidgets{}}}
for _, f := range families {
fmt.Println("---", f.name)
fmt.Println(strings.Join(orderScreen(f.widgets), "\n"))
}
} C++ · compiled/themes.cpp
// Abstract Factory in C++23: an abstract class with one pure virtual creation function per
// product type, one class per family. A family that misses one stays abstract, and creating it
// does not compile.
// Build: clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined themes.cpp -o themes
#include <algorithm>
#include <format>
#include <memory>
#include <print>
#include <string>
#include <vector>
using Row = std::vector<std::string>;
struct WidgetFactory {
virtual ~WidgetFactory() = default;
virtual std::string badge(const std::string& text) const = 0;
virtual std::vector<std::string> table(const Row& header,
const std::vector<Row>& rows) const = 0;
virtual std::string button(const std::string& label) const = 0;
};
std::vector<std::size_t> widths_of(const Row& header, const std::vector<Row>& rows) {
std::vector<std::size_t> widths(header.size());
for (std::size_t i = 0; i < header.size(); ++i) widths[i] = header[i].size();
for (const auto& row : rows)
for (std::size_t i = 0; i < row.size(); ++i)
widths[i] = std::max(widths[i], row[i].size());
return widths;
}
// One table row between bars: the first column left-aligned, the others right-aligned.
std::string cells(const Row& row, const std::vector<std::size_t>& widths,
const std::string& bar) {
std::string line = bar;
for (std::size_t i = 0; i < row.size(); ++i)
line += (i == 0 ? std::format(" {:<{}} ", row[i], widths[i])
: std::format(" {:>{}} ", row[i], widths[i])) +
bar;
return line;
}
std::string rule(const std::vector<std::size_t>& widths, const std::string& left,
const std::string& fill, const std::string& middle, const std::string& right) {
std::string line = left;
for (std::size_t i = 0; i < widths.size(); ++i) {
for (std::size_t k = 0; k < widths[i] + 2; ++k) line += fill;
line += i + 1 < widths.size() ? middle : right;
}
return line;
}
struct AsciiWidgets final : WidgetFactory {
std::string badge(const std::string& text) const override { return "(" + text + ")"; }
std::vector<std::string> table(const Row& header,
const std::vector<Row>& rows) const override {
auto widths = widths_of(header, rows);
auto line = rule(widths, "+", "-", "+", "+");
std::vector<std::string> out{line, cells(header, widths, "|"), line};
for (const auto& row : rows) out.push_back(cells(row, widths, "|"));
out.push_back(line);
return out;
}
std::string button(const std::string& label) const override { return "[ " + label + " ]"; }
};
struct BoxWidgets final : WidgetFactory {
std::string badge(const std::string& text) const override { return "«" + text + "»"; }
std::vector<std::string> table(const Row& header,
const std::vector<Row>& rows) const override {
auto widths = widths_of(header, rows);
std::vector<std::string> out{rule(widths, "┌", "─", "┬", "┐"),
cells(header, widths, "│"),
rule(widths, "├", "─", "┼", "┤")};
for (const auto& row : rows) out.push_back(cells(row, widths, "│"));
out.push_back(rule(widths, "└", "─", "┴", "┘"));
return out;
}
std::string button(const std::string& label) const override { return "▶ " + label + " ◀"; }
};
// The client: it asks the factory for every widget and never names a family.
std::vector<std::string> order_screen(const WidgetFactory& widgets) {
std::vector<std::string> screen{"Order 1042 " + widgets.badge("shipped")};
for (auto& line : widgets.table({"item", "qty"}, {{"kettle", "1"}, {"mug", "2"}}))
screen.push_back(line);
screen.push_back(widgets.button("Track parcel"));
return screen;
}
int main() {
const AsciiWidgets ascii;
const BoxWidgets box;
for (auto [name, family] :
{std::pair<const char*, const WidgetFactory*>{"AsciiWidgets", &ascii},
{"BoxWidgets", &box}}) {
std::println("--- {}", name);
for (const auto& line : order_screen(*family)) std::println("{}", line);
}
} Rust · compiled/themes.rs
// Abstract Factory in Rust: a trait with one creation method per product type, one type per
// family. An impl that misses a method is rejected by the compiler (error E0046).
// Build: rustc --edition 2024 themes.rs
trait WidgetFactory {
fn badge(&self, text: &str) -> String;
fn table(&self, header: &[&str], rows: &[Vec<&str>]) -> Vec<String>;
fn button(&self, label: &str) -> String;
}
fn widths_of(header: &[&str], rows: &[Vec<&str>]) -> Vec<usize> {
(0..header.len())
.map(|i| {
rows.iter()
.map(|row| row[i].chars().count())
.chain([header[i].chars().count()])
.max()
.unwrap_or(0)
})
.collect()
}
// One table row between bars: the first column left-aligned, the others right-aligned.
fn cells(row: &[&str], widths: &[usize], bar: &str) -> String {
let texts: Vec<String> = row
.iter()
.zip(widths)
.enumerate()
.map(|(i, (v, w))| {
if i == 0 {
format!(" {v:<w$} ")
} else {
format!(" {v:>w$} ")
}
})
.collect();
format!("{bar}{}{bar}", texts.join(bar))
}
fn rule(widths: &[usize], left: &str, fill: &str, middle: &str, right: &str) -> String {
let parts: Vec<String> = widths.iter().map(|w| fill.repeat(w + 2)).collect();
format!("{left}{}{right}", parts.join(middle))
}
struct AsciiWidgets;
impl WidgetFactory for AsciiWidgets {
fn badge(&self, text: &str) -> String {
format!("({text})")
}
fn table(&self, header: &[&str], rows: &[Vec<&str>]) -> Vec<String> {
let widths = widths_of(header, rows);
let line = rule(&widths, "+", "-", "+", "+");
let mut out = vec![line.clone(), cells(header, &widths, "|"), line.clone()];
out.extend(rows.iter().map(|row| cells(row, &widths, "|")));
out.push(line);
out
}
fn button(&self, label: &str) -> String {
format!("[ {label} ]")
}
}
struct BoxWidgets;
impl WidgetFactory for BoxWidgets {
fn badge(&self, text: &str) -> String {
format!("«{text}»")
}
fn table(&self, header: &[&str], rows: &[Vec<&str>]) -> Vec<String> {
let widths = widths_of(header, rows);
let mut out = vec![
rule(&widths, "┌", "─", "┬", "┐"),
cells(header, &widths, "│"),
rule(&widths, "├", "─", "┼", "┤"),
];
out.extend(rows.iter().map(|row| cells(row, &widths, "│")));
out.push(rule(&widths, "└", "─", "┴", "┘"));
out
}
fn button(&self, label: &str) -> String {
format!("▶ {label} ◀")
}
}
// The client: it asks the factory for every widget and never names a family.
fn order_screen(widgets: &dyn WidgetFactory) -> Vec<String> {
let mut screen = vec![format!("Order 1042 {}", widgets.badge("shipped"))];
screen.extend(widgets.table(&["item", "qty"], &[vec!["kettle", "1"], vec!["mug", "2"]]));
screen.push(widgets.button("Track parcel"));
screen
}
fn main() {
let families: [(&str, &dyn WidgetFactory); 2] =
[("AsciiWidgets", &AsciiWidgets), ("BoxWidgets", &BoxWidgets)];
for (name, family) in families {
println!("--- {name}");
println!("{}", order_screen(family).join("\n"));
}
} compiled/languages_session.py
from session import run, versions
# The two widget families in five more languages: each program must print the screens that
# themes.py prints.
versions("javac --version", "node --version", "go version", "clang++ --version", "rustc --version")
run("""
java Themes.java
node themes.ts
go run themes.go
clang++ -std=c++23 -O1 -g -Wall -Wextra -fsanitize=address,undefined themes.cpp -o themes
./themes
rustc --edition 2024 -o themes_rs themes.rs && ./themes_rs
""") Output
# javac 25.0.4.1 # v24.21.0 # go version go1.27.1 darwin/arm64 # Apple clang version 21.0.0 (clang-2100.3.34.2) # rustc 1.99.0 (b940084d7) $ java Themes.java --- AsciiWidgets Order 1042 (shipped) +--------+-----+ | item | qty | +--------+-----+ | kettle | 1 | | mug | 2 | +--------+-----+ [ Track parcel ] --- BoxWidgets Order 1042 «shipped» ┌────────┬─────┐ │ item │ qty │ ├────────┼─────┤ │ kettle │ 1 │ │ mug │ 2 │ └────────┴─────┘ ▶ Track parcel ◀ $ node themes.ts --- AsciiWidgets Order 1042 (shipped) +--------+-----+ | item | qty | +--------+-----+ | kettle | 1 | | mug | 2 | +--------+-----+ [ Track parcel ] --- BoxWidgets Order 1042 «shipped» ┌────────┬─────┐ │ item │ qty │ ├────────┼─────┤ │ kettle │ 1 │ │ mug │ 2 │ └────────┴─────┘ ▶ Track parcel ◀ $ go run themes.go --- AsciiWidgets Order 1042 (shipped) +--------+-----+ | item | qty | +--------+-----+ | kettle | 1 | | mug | 2 | +--------+-----+ [ Track parcel ] --- BoxWidgets Order 1042 «shipped» ┌────────┬─────┐ │ item │ qty │ ├────────┼─────┤ │ kettle │ 1 │ │ mug │ 2 │ └────────┴─────┘ ▶ Track parcel ◀ $ clang++ -std=c++23 -O1 -g -Wall -Wextra -fsanitize=address,undefined themes.cpp -o themes $ ./themes --- AsciiWidgets Order 1042 (shipped) +--------+-----+ | item | qty | +--------+-----+ | kettle | 1 | | mug | 2 | +--------+-----+ [ Track parcel ] --- BoxWidgets Order 1042 «shipped» ┌────────┬─────┐ │ item │ qty │ ├────────┼─────┤ │ kettle │ 1 │ │ mug │ 2 │ └────────┴─────┘ ▶ Track parcel ◀ $ rustc --edition 2024 -o themes_rs themes.rs && ./themes_rs --- AsciiWidgets Order 1042 (shipped) +--------+-----+ | item | qty | +--------+-----+ | kettle | 1 | | mug | 2 | +--------+-----+ [ Track parcel ] --- BoxWidgets Order 1042 «shipped» ┌────────┬─────┐ │ item │ qty │ ├────────┼─────┤ │ kettle │ 1 │ │ mug │ 2 │ └────────┴─────┘ ▶ Track parcel ◀
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 languages_session.py
compiled/session.py
"""Prints a terminal session: "$ " and each command, then everything it printed (standard output
and standard error together), and its exit status when that is not 0.
The toolchains are the pinned ones: Java 25 (Eclipse Temurin 25.0.4), Node.js 24.21.0 and Go
1.27.1 from the mise installations next to the Python that runs this file, Rust 1.99.0 from
its rustup toolchain, and the system's Apple clang. learn:verify cannot let a sandboxed run
read the home folder those installations live in, so the sessions of this folder are recorded
with LEARN_VERIFY_NETWORK=allow. They read only this folder and the toolchains, write only
inside this folder and the run's own temporary home, and never use the network.
A command that fails and is not listed in expect_failure makes the session exit with status
1, so a run that could not build a program never becomes a record."""
import os
import pwd
import re
import signal
import subprocess
import sys
from pathlib import Path
# .../installs/python/3.14.8/bin/python3 -> .../installs
INSTALLS = Path(sys.executable).resolve().parents[3]
REAL_HOME = Path(pwd.getpwuid(os.getuid()).pw_dir)
TOOLCHAINS = [
INSTALLS / "java" / "temurin-25.0.4+101.0.LTS" / "bin",
INSTALLS / "node" / "24.21.0" / "bin",
INSTALLS / "go" / "1.27.1" / "bin",
REAL_HOME / ".rustup" / "toolchains" / "1.99.0-aarch64-apple-darwin" / "bin",
]
# Version lines that end with a build date (rustc's) are shown without it: pages show no dates.
BUILD_DATE = re.compile(r"\s+\d{4}-\d{2}-\d{2}(?=\))")
def _env() -> dict[str, str]:
env = dict(os.environ)
env["PATH"] = os.pathsep.join([*map(str, TOOLCHAINS), env.get("PATH", "")])
env["GOTOOLCHAIN"] = "local" # never download another Go
env["GOTELEMETRY"] = "off"
env["GOFLAGS"] = "-trimpath"
return env
def _shell(command: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
command, shell=True, env=_env(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
)
def versions(*commands: str) -> None:
"""Prints the first line of each version command as a comment."""
for command in commands:
done = _shell(command)
lines = done.stdout.splitlines()
if done.returncode != 0 or not lines:
sys.exit(f"{command} did not run: the pinned toolchain is missing here")
print("#", BUILD_DATE.sub("", lines[0]))
def run(script: str, expect_failure: tuple[str, ...] = ()) -> None:
"""Runs each line of the script as one shell command; only the commands in expect_failure
may fail."""
failed = 0
for command in (line.strip() for line in script.strip().splitlines()):
print(f"$ {command}")
done = _shell(command)
print(done.stdout, end="")
if done.returncode < 0:
print(f"[stopped by signal {signal.Signals(-done.returncode).name}]")
elif done.returncode != 0:
print(f"[exit status {done.returncode}]")
if done.returncode != 0 and command not in expect_failure:
failed += 1
if failed:
sys.exit(1) In the compiled languages, the check that Python makes when an object is created happens before the program runs.
A Java class that implements WidgetFactory without badge() does not compile:
compiled/missing_session.py
from session import run
# A family that forgets a product type: Java refuses to compile it, so the program never runs.
run("""
java MissingProduct.java
""", expect_failure=("java MissingProduct.java",)) Output
$ java MissingProduct.java
MissingProduct.java:15: error: HalfWidgets is not abstract and does not override abstract method badge(String) in WidgetFactory
static final class HalfWidgets implements WidgetFactory {
^
1 error
error: compilation failed
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 missing_session.py
compiled/MissingProduct.java
// A family that forgets one product type does not compile: the compiler checks every family
// against the factory interface.
// Run: java MissingProduct.java (it is meant to fail)
import java.util.List;
public class MissingProduct {
interface WidgetFactory {
String badge(String text);
List<String> table(List<String> header, List<List<String>> rows);
String button(String label);
}
static final class HalfWidgets implements WidgetFactory {
public List<String> table(List<String> header, List<List<String>> rows) {
return List.of();
}
public String button(String label) {
return label;
}
}
public static void main(String[] args) {
System.out.println(new HalfWidgets().button("Track parcel"));
}
} The others refuse in their own words. In C++ a class that leaves a pure virtual function unimplemented stays
abstract, and creating an object of it is a compile error. Rust reports error E0046 for an impl that misses a
method of its trait. Go has no implements keyword: a struct satisfies an interface by having all its methods, and
using one that does not, wherever a WidgetFactory is expected, fails to compile with a message that names the
missing method. TypeScript reports the same mistake when tsc checks the project; Node.js itself only strips the
types when it runs the file.
In the JDK: XML parsers and documents
Java’s XML API is built out of factories, and two layers of it fit this lesson.
compiled/dom_session.py
from session import run
# The factories of the JDK's XML API: a configured factory of parsers, and documents that create
# their own nodes.
run("""
java Dom.java
""") Output
$ java Dom.java factory: com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl refused: DOCTYPE is disallowed when the feature "http://apache.org/xml/features/disallow-doctype-decl" set to true. document: com.sun.org.apache.xerces.internal.dom.DocumentImpl built: <order id="1042"><item>kettle</item><!--shipped--></order> mixed: DOMException, WRONG_DOCUMENT_ERR true: WRONG_DOCUMENT_ERR: A node is used in a different document than the one that created it. adopted: <total> belongs to the invoice: true
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 dom_session.py
compiled/Dom.java
// The JDK's XML API is made of factories. Run: java Dom.java
import java.io.StringReader;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
public class Dom {
public static void main(String[] args) throws Exception {
// 1. A factory of parsers. Configure it once, as the OWASP XXE cheat sheet advises;
// every builder it makes inherits the configuration.
DocumentBuilderFactory factory = DocumentBuilderFactory.newDefaultInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
System.out.println("factory: " + factory.getClass().getName());
DocumentBuilder parser = factory.newDocumentBuilder();
parser.setErrorHandler(new Quiet());
try {
String withDoctype = "<!DOCTYPE order [<!ENTITY x \"y\">]><order>&x;</order>";
parser.parse(new InputSource(new StringReader(withDoctype)));
} catch (SAXParseException e) {
System.out.println("refused: " + e.getMessage());
}
// 2. A Document is a factory too: its elements, texts and comments are created by the
// document they belong to.
Document order = factory.newDocumentBuilder().newDocument();
System.out.println("document: " + order.getClass().getName());
Element root = order.createElement("order");
root.setAttribute("id", "1042");
Element item = order.createElement("item");
item.appendChild(order.createTextNode("kettle"));
root.appendChild(item);
root.appendChild(order.createComment("shipped"));
order.appendChild(root);
System.out.println("built: " + xml(root));
// 3. A node made by one document cannot be added to another one ...
Document invoice = factory.newDocumentBuilder().newDocument();
try {
invoice.appendChild(order.createElement("total"));
} catch (DOMException e) {
boolean wrong = e.code == DOMException.WRONG_DOCUMENT_ERR;
System.out.println(
"mixed: DOMException, WRONG_DOCUMENT_ERR " + wrong + ": " + e.getMessage());
}
// ... until the other document adopts it, which makes it that document's node.
Node total = invoice.adoptNode(order.createElement("total"));
invoice.appendChild(total);
boolean owned = total.getOwnerDocument() == invoice;
String name = total.getNodeName();
System.out.println("adopted: <" + name + "> belongs to the invoice: " + owned);
}
/** Throws on every problem instead of printing it. */
static final class Quiet implements ErrorHandler {
public void warning(SAXParseException e) throws SAXException {
throw e;
}
public void error(SAXParseException e) throws SAXException {
throw e;
}
public void fatalError(SAXParseException e) throws SAXException {
throw e;
}
}
/** A small serialiser for elements, attributes, texts and comments. */
static String xml(Node node) {
return switch (node.getNodeType()) {
case Node.TEXT_NODE -> node.getNodeValue();
case Node.COMMENT_NODE -> "<!--" + node.getNodeValue() + "-->";
default -> {
StringBuilder out = new StringBuilder("<" + node.getNodeName());
NamedNodeMap attributes = node.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node a = attributes.item(i);
out.append(' ').append(a.getNodeName());
out.append("=\"").append(a.getNodeValue()).append('"');
}
out.append('>');
Node child = node.getFirstChild();
for (; child != null; child = child.getNextSibling()) {
out.append(xml(child));
}
yield out.append("</").append(node.getNodeName()).append('>').toString();
}
};
}
} DocumentBuilderFactory is an abstract class. Its documentation describes it as a factory API that lets
applications obtain a parser producing DOM trees, and newInstance() chooses the implementation class at run time
through the JAXP lookup mechanism; newDefaultInstance(), used here, returns the JDK’s built-in implementation,
whose class name is the first line of the output. Configuration travels with the factory: the two settings that the
OWASP XML External Entity Prevention Cheat Sheet gives for DOM parsing are made once, and every builder the factory
makes refuses a DOCTYPE, which closes the door on external entity attacks.
The second layer is the family. A DOM Document is itself a factory: createElement, createTextNode and
createComment all live on it, because, as its documentation explains, such nodes cannot exist outside the context
of a document, and each node records the document that created it as its owner. Java’s DOM enforces the family rule
strictly. Appending an element made by one document to another throws a DOMException with the code
WRONG_DOCUMENT_ERR, and the node must be adopted (adoptNode moves it) or imported (importNode copies it) first.
Passing the family without a factory class
A hand-written factory interface is not the only way to keep a family together. If the family is chosen once, when the program starts, the simplest design is often to build its parts at that moment and hand them over as one object: a small class or dataclass that holds the configured repositories, a dependency-injection module, or in Python a module or a dictionary of callables. Choosing the family in one place at start-up, the composition root, gives the same guarantee that nobody mixes parts by accident.
OrderService above already works that way: its store arrives as a constructor parameter, so the service has no idea
which family it was given. A factory interface earns its place when code must create new products again and again
while it runs, such as one repository per request or one widget per row, or when other people supply the families,
such as plug-in themes. When there is only one family and no test double, there is nothing to vary, and plain
constructors are clearer.
When not to use it
- New product types arrive more often than new families. Every new type edits every factory, so the pattern makes the common change the expensive one.
- There is one family. An interface with a single implementation is indirection with no payoff; add the factory when the second family, or a fake for tests, actually appears.
- The factory starts handing out shared objects instead of creating them. A factory that returns one global database connection or one global cache has become a service locator: classes call it to fetch their dependencies, which hides those dependencies from their constructors and their tests. Keep factories creating, and pass shared objects in.
Exercise: a new family and a new product type
Exercise · Medium · Python
Add a widget family and a new widget type
widgets.py has an abstract factory, WidgetFactory, with two product types (button and badge) and two families (AsciiWidgets and BoxWidgets). Make both kinds of change the lesson describes, and see what each costs:
1. **A new product type: tooltip(text).** Add it to WidgetFactory as an abstract method, then to both families: AsciiWidgets().tooltip("Saved") returns (?) Saved and BoxWidgets().tooltip("Saved") returns ⓘ Saved. The help screen gets a tooltip line between its two lines: widgets.tooltip("Press Esc to close"). 2. **A new family: HighContrastWidgets.** It writes every text in capital letters, with marks of its own: the button [[ CLOSE ]], the badge ** NEW ** and the tooltip !! SAVED. Add it to FAMILIES under the name high-contrast.
Also make create_factory refuse a family it does not know with ValueError, naming it in double quotes and listing the known ones in alphabetical order: no widget family "sepia" (known: ascii, box, high-contrast).
The sample tests check every widget of every family, the three help screens, that a high-contrast screen contains no mark of the other families, and that WidgetFactory has exactly three abstract methods. Run them when you are done.
Starter code · widgets.py
"""Two families of console widgets behind one abstract factory, and a help screen built through one factory."""
from abc import ABC, abstractmethod
class WidgetFactory(ABC):
"""One creation method per product type. Every family implements all of them."""
@abstractmethod
def button(self, label): ...
@abstractmethod
def badge(self, text): ...
class AsciiWidgets(WidgetFactory):
def button(self, label):
return "[ " + label + " ]"
def badge(self, text):
return "(" + text + ")"
class BoxWidgets(WidgetFactory):
def button(self, label):
return "▶ " + label + " ◀"
def badge(self, text):
return "«" + text + "»"
FAMILIES = {"ascii": AsciiWidgets, "box": BoxWidgets}
def create_factory(name):
"""Returns a new factory of the family called `name`."""
return FAMILIES[name]()
def help_screen(widgets):
"""The lines of the help screen. Every widget comes from the one factory it is given."""
return ["Help " + widgets.badge("shortcuts"), widgets.button("Close")] The sample tests · test_widgets.py
from widgets import FAMILIES, AsciiWidgets, BoxWidgets, WidgetFactory, create_factory, help_screen
# Characters that only one family uses: a high-contrast screen must contain none of the others'.
ASCII_MARKS = ["(?)", "(", ")"]
BOX_MARKS = ["▶", "◀", "«", "ⓘ"]
def test_tooltip_is_a_product_type():
"""WidgetFactory has three abstract creation methods: button, badge and tooltip"""
assert set(WidgetFactory.__abstractmethods__) == {"button", "badge", "tooltip"}
def test_three_families():
"""FAMILIES knows ascii, box and high-contrast, and each one can be created"""
assert sorted(FAMILIES) == ["ascii", "box", "high-contrast"]
for name in FAMILIES:
assert isinstance(create_factory(name), WidgetFactory), name
def test_ascii_family():
"""the ascii family keeps its widgets and adds a tooltip"""
w = AsciiWidgets()
assert (w.button("Close"), w.badge("new"), w.tooltip("Saved")) == ("[ Close ]", "(new)", "(?) Saved")
def test_box_family():
"""the box family keeps its widgets and adds a tooltip"""
w = BoxWidgets()
assert (w.button("Close"), w.badge("new"), w.tooltip("Saved")) == ("▶ Close ◀", "«new»", "ⓘ Saved")
def test_high_contrast_family():
"""the high-contrast family writes every text in capitals, with its own marks"""
w = create_factory("high-contrast")
assert (w.button("Close"), w.badge("new"), w.tooltip("Saved")) == ("[[ CLOSE ]]", "** NEW **", "!! SAVED")
def test_help_screens():
"""the help screen has a tooltip line between the badge and the button, in every family"""
assert help_screen(create_factory("ascii")) == ["Help (shortcuts)", "(?) Press Esc to close", "[ Close ]"]
assert help_screen(create_factory("box")) == ["Help «shortcuts»", "ⓘ Press Esc to close", "▶ Close ◀"]
assert help_screen(create_factory("high-contrast")) == ["Help ** SHORTCUTS **", "!! PRESS ESC TO CLOSE", "[[ CLOSE ]]"]
def test_no_mixing():
"""a high-contrast screen contains no mark of the other two families"""
text = "\n".join(help_screen(create_factory("high-contrast")))
for mark in ASCII_MARKS + BOX_MARKS:
assert mark not in text, f"{mark!r} belongs to another family"
def test_unknown_family():
"""an unknown family raises ValueError that names it and lists the known ones"""
try:
create_factory("sepia")
except ValueError as error:
message = str(error)
assert '"sepia"' in message, message
assert "ascii, box, high-contrast" in message, message
else:
raise AssertionError('create_factory("sepia") should raise ValueError') A hint
Start with the product type: once tooltip is an abstract method of WidgetFactory, Python refuses to create any family that lacks it, so the error messages list what is left to write. HighContrastWidgets derives from WidgetFactory like the others and can call .upper() on each text before adding its marks.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
Check yourself
5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
Interview questions
Fresher · How does Abstract Factory differ from Factory Method?
Factory Method is one overridable method that creates one product; a subclass decides which class it is. Abstract Factory is an object with several creation methods, one per product type of a family, and each concrete factory implements all of them for one family, such as a light or dark theme or a production or test storage back-end. The client is given one factory and gets every product from it, so the products always belong together, which Factory Method alone does not guarantee. The cost differs too: a new family is one new factory class, while a new product type adds a method to every factory. A good answer adds that each creation method of an Abstract Factory is often written as a factory method, so the two patterns are frequently used together.
Key takeaways
- An Abstract Factory has one creation method per product type, and each concrete factory implements all of them for one family, so a client that holds one factory cannot mix families.
- Read the family grid by columns and rows: a new family is a new column with no edits, a new product type is a new row that edits every factory, which is the expression problem again.
- The products of one family often share something their correctness depends on, such as one database transaction; mixing families breaks it silently, as the ghost order showed.
- Compilers check that every family makes every product (Java, C++, Rust and Go at compile time, Python when an object is created).
- The JDK’s
DocumentBuilderFactoryand DOMDocumentare factories; a node belongs to the document that created it. - When the family is chosen once, a configured object or dependency injection is often simpler than a factory interface.
References
- Design Patterns: Elements of Reusable Object-Oriented Software (Abstract Factory) (Addison-Wesley (Pearson))
- DocumentBuilderFactory (Java SE 25) (Oracle)
- Document (Java SE 25, org.w3c.dom) (Oracle)
- Node (Java SE 25, org.w3c.dom), appendChild and WRONG_DOCUMENT_ERR (Oracle)
- XML External Entity Prevention Cheat Sheet (OWASP Foundation)
- The Expression Problem (Philip Wadler)
- sqlite3: how to use the connection context manager (Python Software Foundation)
- abc: abstract base classes (Python Software Foundation)
- Rust compiler error index: E0046 (The Rust Project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress