Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, Java 25, Go 1.27.1, Clang 21.0.0 and Rust 1.99.0
  • By MySmartCoPilot
Free: 30 minutes

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

Abstract Factory as a grid: product types are rows, families are columns, and every cell is one concrete product.WidgetFactoryNew family: one more column, nothing editedNew product type: one more row, every factory editedproduct typeAsciiWidgetsBoxWidgetsbadge()round bracketsguillemets « »table()+ - | linesbox linesbutton()[ square ]arrowheads

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() and button().
  • Each concrete factory is one family: AsciiWidgets and BoxWidgets.
  • The products are what the methods return. In the classic catalogue each product type has an interface of its own (a Button, a Badge); in this short example they are lines of text, which keeps the focus on the factory.
  • The client, order_screen(), takes a WidgetFactory and 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

Two widget families and a client that never names one Python · themes/themes.py
"""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

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

One service, two storage families and one store that mixes them

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()

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 sqlite3 connection 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.
  • MixedStore takes 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.

SQLite Online (SQL Playground) Run the CREATE TABLE and INSERT statements of stores.py and try a transaction that rolls back.

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.

The widget families in Java, TypeScript, Go, C++ and Rust

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"));
    }
}
All five built and run with the pinned toolchains

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:

A family without a badge, rejected by the Java compiler

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.

A configured factory of parsers, and documents that create their own nodes

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.

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.

Python Online Compiler Paste widgets.py and its tests into the compiler to run them outside the lesson.

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.

  1. Question 1 of 5 What does an Abstract Factory give the client that three separate factory functions would not?

    Choose one answer.

    Show the answer to question 1

    Answer: Products that all come from the same family, because one factory object makes all of them

    The client receives one factory object and asks it for every product, so a screen gets all its widgets, or a service all its repositories, from one family. Separate functions can be called from different families by mistake, which is what the mixed examples of the lesson do. It does not reduce the number of classes, and adding a product type is the change it makes expensive.

  2. Question 2 of 5 A factory interface has 4 product types and 3 families. You add a fifth product type. How many existing factory classes must change?

    Type a number.

    Show the answer to question 2

    Answer: 3 factories

    Every family must make the new product, so each of the 3 existing factories gets a new method (and 3 new product classes are written). Adding a fourth family instead would change no existing factory: it is one new factory and its 5 products.

  3. Question 3 of 5 In the storage example, MixedStore keeps orders in memory but takes its stock and its transaction from SQLite. Why does the third, refused order stay in its order list?

    Choose one answer.

    Show the answer to question 3

    Answer: Because the rollback belongs to the SQLite family and cannot undo a change made by an object of the memory family

    The products of one family share something that their correctness depends on, here one transaction. SQLite rolls back its own work, which is nothing, while the order was added to a Python list that no transaction covers. The two real families each undo both changes; only the mixed store keeps a ghost order.

  4. Question 4 of 5 In Java, an element created by document A is passed to appendChild on document B. What happens?

    Choose one answer.

    Show the answer to question 4

    Answer: A DOMException with the code WRONG_DOCUMENT_ERR, unless B adopts or imports the node first

    A DOM Document is a factory of its own nodes, and every node remembers its owner document. Java's DOM refuses a node made by another document; adoptNode (which moves it) or importNode (which copies it) makes it a node of B first, as Dom.java shows.

  5. Question 5 of 5 When is a plain object or module that bundles the family's parts simpler than a hand-written Abstract Factory?

    Choose every answer that is right.

    Show the answer to question 5

    Answer:

    • When there is only ever one family and no test double
    • When the family is chosen once, at start-up, and never changes while the program runs
    • In Python, when a dictionary of functions or a module already holds the family

    A family chosen once can be wired at start-up and handed over as one configured object; in Python a module or a dictionary of callables does the same. With only one family there is nothing to vary, so a factory interface is indirection without benefit. Code that creates fresh products while it runs is where a factory still earns its place.

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 DocumentBuilderFactory and DOM Document are 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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.