CHAPTER 6 · DESIGN PATTERNS

Pattern Fusion — Designing a Document Editor

A single pattern almost never ships alone. Real systems fuse them — one design problem after another, each answered by a pattern, until a whole architecture emerges. In this chapter we design a word-processor (call it Lexi, after the classic Gang-of-Four case study) from a blank page. Every requirement raises a design question; we look at the naive answer, watch it break under one concrete change request, and land on the pattern that fixes it — in idiomatic C#. By the end you'll see the nine patterns cooperating around a single Document core.

🧠 Why this matters

Chapters 35 taught the patterns one at a time, in isolation. That is not how you meet them in the wild. The skill this chapter builds is the one interviews and real projects actually test: given a messy requirement, recognise which pattern the situation is asking for — and how it plugs into the ones already there.

Patterns are a vocabulary, not a checklist

A beginner reads the 23 patterns as 23 gadgets and asks "which one do I use?" That is the wrong frame. Patterns are a shared vocabulary for design conversations. When a teammate says "make the border a Decorator so scrollbars stack the same way," a whole paragraph of intent travels in one word. The value is compression: named solutions let a team reason about structure out loud.

And because each pattern solves one kind of problem, a realistic feature touches several at once. Saving a document that you can also spell-check, undo, and render on two operating systems is not one problem — it is five, braided together. So the patterns braid too. Watch for three kinds of relationship as we go:

Layered. One pattern sits on top of another — a Visitor walks a Composite tree; a Memento stores what a Command needs to undo.
Substitutable. Two patterns can solve overlapping problems, so you pick — or combine. Decorator and Composite share an interface on purpose.
Habitually paired. Some pairs show up together so often they are almost idioms: Abstract Factory + Singleton, Command + Memento, Composite + Iterator.
🌍 Real-world analogy

Think of patterns like chess openings. A club player memorises individual moves; a strong player thinks in named sequences ("Sicilian Defence") and in how one opening transposes into another. The names don't win the game — but they let you plan several moves ahead and talk about the plan.

The editor brief

Here is the product brief, distilled from the lecture slides and rounded out to the full classic document-editor exercise. Read each line as a design pressure, not just a feature — the phrasing hints at what will change later, and change is what patterns defend against.

#RequirementThe pressure it createsPattern we'll reach for
1A document is a tree of characters, images, rows, columns, tables and pages, edited uniformly.Treat a single glyph and a whole table the same way.Composite
2A page holds hundreds of thousands of characters.One object per character would blow up memory.Flyweight
3Build documents from headers, paragraphs and sections fluently.Assemble a complex tree without a giant constructor.Builder
4Text must flow into lines — and different quality/speed trade-offs are wanted.Swap the line-breaking algorithm without touching the document.Strategy
5Optional borders and scrollbars around any element.Add responsibilities to individual objects, stackably, at run time.Decorator
6Match multiple look-and-feel standards (Windows, macOS…).Create families of matching widgets without hard-coding one.Abstract Factory
7Run on multiple window systems.Vary the widget abstraction and the platform independently.Bridge
8Every edit is undoable and redoable.Encapsulate actions so they can be logged, queued and reversed.Command + Memento
9Spell-check and hyphenation sweep the whole document.Walk the tree, and add new analyses without editing element classes.Iterator + Visitor

We'll take them in an order that lets each pattern build on the last. Skip ahead with the sidebar, or play with the design explorer and collaboration map once you've read a few.

1 · Document structure → Composite

Composite · structural

The requirement. A document isn't flat text. A character sits in a line, lines stack into a column, columns arrange on a page, and an image or a table can appear anywhere a character can. The editor must let the user select, move, draw and measure any of these — a single letter or an entire nested table — with the same operations.

The design question. How do we treat a leaf (one character) and a container (a table full of characters) uniformly, so that code which lays out or draws the document never has to ask "is this one thing or a group of things?"

❌ Naive: separate types, branching everywhere
void Draw(object node) {
    if (node is Character c)      DrawChar(c);
    else if (node is Image img)   DrawImage(img);
    else if (node is Row row)     foreach (var k in row.Kids) Draw(k);
    else if (node is Column col)  foreach (var k in col.Kids) Draw(k);
    // ...a new element type = edit this method, and Measure(), and Select()...
}
Every operation (draw, measure, select, hit-test) grows the same if/else ladder. Adding a Table means editing them all. This is exactly the Open/Closed violation from Chapter 2.
✅ Composite: one interface for leaf and group
Draw(page);   // page recurses into columns,
              // columns into rows, rows into glyphs —
              // the caller never branches on type.
Leaves and containers implement the same interface. Recursion lives inside the containers, not in every algorithm that visits the tree.

The solution. Define one abstraction — call it Glyph, the GoF name for "anything that appears in the document" — with the operations every element supports. A leaf implements them directly; a composite implements them by delegating to its children. This is the Composite pattern: compose objects into tree structures, then treat individual objects and compositions identically.

Glyph.cs — the shared abstraction
public interface IGlyph
{
    void Draw(Window w);           // paint yourself
    Rect Bounds();                 // where you are
    bool Intersects(Point p);      // hit-testing for the mouse

    // Child-management: present on the interface so callers stay uniform.
    void Insert(IGlyph child, int at);
    void Remove(IGlyph child);
    IGlyph Child(int i);
    IEnumerable<IGlyph> Children();
}
// A leaf has no children, so child-ops are no-ops (or throw).
public sealed class Character : IGlyph
{
    private readonly char _c;
    public Character(char c) => _c = c;

    public void Draw(Window w)      => w.DrawChar(_c, Bounds());
    public Rect Bounds()            => /* font metrics */ new Rect();
    public bool Intersects(Point p) => Bounds().Contains(p);

    public void Insert(IGlyph child, int at) =>
        throw new NotSupportedException("A character has no children.");
    public void Remove(IGlyph child)         => throw new NotSupportedException();
    public IGlyph Child(int i)               => throw new NotSupportedException();
    public IEnumerable<IGlyph> Children()     => Array.Empty<IGlyph>();
}
// A composite forwards each operation to its children.
public class Row : IGlyph            // Column, Page, Table all look like this
{
    private readonly List<IGlyph> _kids = new();

    public void Draw(Window w)      { foreach (var k in _kids) k.Draw(w); }
    public Rect Bounds()            => Rect.Union(_kids.Select(k => k.Bounds()));
    public bool Intersects(Point p) => _kids.Any(k => k.Intersects(p));

    public void Insert(IGlyph child, int at) => _kids.Insert(at, child);
    public void Remove(IGlyph child)         => _kids.Remove(child);
    public IGlyph Child(int i)               => _kids[i];
    public IEnumerable<IGlyph> Children()     => _kids;
}
// The client builds a tree and treats every node the same way.
IGlyph page = new Page();
IGlyph col  = new Column();
IGlyph row  = new Row();
row.Insert(new Character('H'), 0);
row.Insert(new Character('i'), 1);
row.Insert(new Image("logo.png"), 2);   // an image sits where a char can
col.Insert(row, 0);
page.Insert(col, 0);

page.Draw(window);            // recurses to every leaf — no type checks
Console.WriteLine(page.Bounds());   // whole-page extent, computed by delegation
IGlyph Draw · Bounds · Insert Character / Image leaf Row / Column / Page composite → has children children: 0..* a real tree ↓ below
Figure 1: One interface (IGlyph); leaves and containers implement it. The child link is a composition (the composite owns its children), and children are themselves IGlyph — so the tree can nest to any depth.
⚠️ The safety-vs-transparency trade-off

Putting Insert/Remove on the shared interface makes every node look the same (transparency) — but lets someone call character.Insert(...), which is meaningless. The alternative — child-ops only on composites — is safe but forces callers to type-check again, losing the whole point. GoF choose transparency, and so do we: leaves throw NotSupportedException. Choose your poison consciously.

2 · A million characters → Flyweight

Flyweight · structural

The requirement. Composite told us every character is an object. Lovely — until you open a 300-page report. That's easily a million Character objects. If each carries its own font, size, colour and position, the document won't fit in memory, and just allocating them stutters the UI.

The design question. The letter 'e' appears tens of thousands of times, always drawn the same way. Why store its font and shape ten thousand times? Can many 'e' positions in the document share one 'e' object?

The naive option and why it fails. Give each Character fields for char, Font, Colour, Point position. Simple — and it multiplies the heaviest data (the font/shape) by the number of occurrences. The change request that kills it: "support long documents on a laptop."

The solution. Split each character's data into two kinds:

That is Flyweight: share fine-grained objects by moving their varying state outside them. A factory hands out shared instances so identical glyphs are created once.

GlyphFactory.cs — shared intrinsic state
// The flyweight: immutable, context-free, safe to share.
public sealed class CharGlyph
{
    private readonly char _symbol;
    public CharGlyph(char symbol) => _symbol = symbol;

    // Extrinsic state (position, font) is a PARAMETER, not a field.
    public void Draw(Window w, Point where, Font font) =>
        w.DrawChar(_symbol, where, font);
}

// The factory guarantees one shared object per character.
public sealed class GlyphFactory
{
    private readonly Dictionary<char, CharGlyph> _pool = new();

    public CharGlyph Get(char c)
    {
        if (!_pool.TryGetValue(c, out var g))
            _pool[c] = g = new CharGlyph(c);   // created once, reused forever
        return g;
    }
    public int Distinct => _pool.Count;
}
Usage — the tree stores only positions
var factory = new GlyphFactory();
foreach (var (c, pos, font) in typedText)   // "eee..." reuses ONE 'e'
    factory.Get(c).Draw(window, pos, font);

Console.WriteLine($"Rendered thousands of chars from {factory.Distinct} glyph objects");
// e.g. "Rendered thousands of chars from 62 glyph objects"
🔀 How Flyweight and Composite coexist

They seem to fight — Composite says "each glyph is a node in the tree," Flyweight says "share glyph objects." The reconciliation: the tree holds lightweight position/index nodes, and each node refers to a shared flyweight for its shape. The shared object can't know its position (many parents share it), so position is passed as extrinsic state at draw time. Chapter 12 (Confusable Patterns) has the full side-by-side.

ℹ️ Where .NET does this

String interning (string.Intern) is Flyweight: identical string literals share one instance. Brushes.Red and Pens.Black in GDI+/WPF are cached shared flyweights too — you never new a red brush per shape.

3 · Assembling documents → Builder

Builder · creational

The requirement. Programs and templates need to construct documents: a report template adds a title header, an intro paragraph, then sections with headings and body text. The slides call this out explicitly, and suggest a fluent interface for ease of use.

The design question. How do we assemble a deeply nested Composite tree without either a monstrous constructor (new Page(new Column(new Row(new Character(... — unreadable) or scattering the tree-wiring logic across the whole app?

The solution. Builder: separate the construction of a complex object from its representation. A DocumentBuilder exposes intent-revealing steps (AddHeading, AddParagraph, BeginSection) and hides the Composite plumbing. Return this from each step and you get the fluent chain the slides ask for.

DocumentBuilder.cs — fluent construction
public sealed class DocumentBuilder
{
    private readonly Page _page = new();
    private Column _current;

    public DocumentBuilder()  => _page.Insert(_current = new Column(), 0);

    public DocumentBuilder AddHeading(string text, int level = 1)
    {
        var row = new Row();
        foreach (var c in text) row.Insert(new Character(c), row.Count);
        row.Style = $"h{level}";
        _current.Insert(row, _current.Count);
        return this;                       // ← fluent: enables chaining
    }

    public DocumentBuilder AddParagraph(string text)
    {
        var row = new Row();
        foreach (var c in text) row.Insert(new Character(c), row.Count);
        _current.Insert(row, _current.Count);
        return this;
    }

    public DocumentBuilder BeginSection()
    {
        _page.Insert(_current = new Column(), _page.Count);
        return this;
    }

    public IGlyph Build() => _page;        // hand back the finished tree
}
Reads like the document it builds
IGlyph doc = new DocumentBuilder()
    .AddHeading("Quarterly Report", level: 1)
    .AddParagraph("Revenue grew 12% this quarter.")
    .BeginSection()
        .AddHeading("Regional Breakdown", level: 2)
        .AddParagraph("EMEA led with 40% of new bookings.")
    .Build();
💡 Builder vs Abstract Factory here

Both are creational and both appear in this design, but they answer different questions. Builder assembles one complex product step by step (the document tree). Abstract Factory creates families of related products in one shot (a whole matching widget set). You'll use Builder to make documents and Abstract Factory to make the UI around them.

4 · Line-breaking → Strategy

Strategy · behavioral

The requirement. Text has to flow: given a column width, decide where each line breaks. And there is no single right algorithm — a fast greedy break is fine while typing, but for print you want a slower, higher-quality break (like TeX's whole-paragraph optimiser) that minimises ragged edges.

The design question. The document structure (Composite) shouldn't change when we swap line-breaking algorithms. How do we make the formatting algorithm a pluggable, replaceable thing?

❌ Naive: bake it into Column
class Column {
    public void Reflow(int width) {
        // 40 lines of greedy line-breaking, hard-wired.
        // Want TeX-quality? Edit this class.
        // Want to A/B two algorithms? You can't.
    }
}
The formatting policy is welded to the data structure. One algorithm, forever, and Column now has two reasons to change (structure and formatting) — an SRP smell.
✅ Strategy: inject the algorithm
column.Compositor = new SimpleCompositor(); // fast
// later, for print:
column.Compositor = new TeXCompositor();    // pretty
column.Reflow(width);   // same call, new behaviour
The algorithm is an object you hand in. New algorithm = new class; the document never changes. This is encapsulate what varies in action.

The solution. Pull line-breaking into its own interface — GoF names it the Compositor — and let a Column hold a reference to one. Strategy: define a family of interchangeable algorithms and make them swappable at run time.

Compositor.cs — pluggable formatting
public interface ICompositor                     // the Strategy
{
    IReadOnlyList<Line> Compose(IReadOnlyList<IGlyph> glyphs, int width);
}

public sealed class SimpleCompositor : ICompositor   // greedy: fast
{
    public IReadOnlyList<Line> Compose(IReadOnlyList<IGlyph> g, int width)
        => GreedyBreak(g, width);
}

public sealed class TeXCompositor : ICompositor      // optimal: pretty
{
    public IReadOnlyList<Line> Compose(IReadOnlyList<IGlyph> g, int width)
        => MinimiseRaggedness(g, width);
}

public class Column : IGlyph
{
    public ICompositor Compositor { get; set; } = new SimpleCompositor();
    public void Reflow(int width) => Layout(Compositor.Compose(Children().ToList(), width));
    // ...IGlyph members omitted...
}
ℹ️ Where .NET does this

List<T>.Sort(IComparer<T>) takes a Strategy — the comparer is the swappable algorithm. System.Text.Json converters and ASP.NET's authorization handlers are Strategy objects too.

5 · Borders & scrollbars → Decorator

Decorator · structural

The requirement. The user should be able to put a border around any element, and a scrollbar on any view — and combine them (a bordered, scrollable text area). These embellishments are optional and chosen at run time.

The design question. How do we add a border to something without making "bordered" a new subclass of every element, and without the element even knowing it's been bordered?

The naive option and why it fails. Subclass: BorderedColumn, ScrollableColumn, BorderedScrollableColumn… Now add "shadow" and the subclass count explodes combinatorially. The change request that kills it: "borders should also work on images and tables." You'd re-derive the whole hierarchy.

The solution. A border is a glyph that wraps another glyph: it draws the child, then draws a frame around it. Because it implements IGlyph, anything that accepts a glyph accepts a bordered glyph — including another decorator. That is Decorator: attach responsibilities to an object dynamically by wrapping it in an object of the same interface. Decorator deliberately shares IGlyph with Composite, which is why decorators drop straight into the tree.

Decorators.cs — wrap, don't subclass
// Base decorator: forwards everything to the wrapped glyph.
public abstract class GlyphDecorator : IGlyph
{
    protected readonly IGlyph Inner;
    protected GlyphDecorator(IGlyph inner) => Inner = inner;

    public virtual void Draw(Window w)      => Inner.Draw(w);
    public virtual Rect Bounds()            => Inner.Bounds();
    public virtual bool Intersects(Point p) => Inner.Intersects(p);
    // child-ops delegate to Inner as well...
}

public sealed class Border : GlyphDecorator
{
    private readonly int _width;
    public Border(IGlyph inner, int width = 1) : base(inner) => _width = width;

    public override void Draw(Window w)     // add behaviour, then call inner
    {
        Inner.Draw(w);
        w.DrawRect(Bounds().Inflate(_width));
    }
    public override Rect Bounds() => base.Bounds().Inflate(_width);
}

public sealed class Scroller : GlyphDecorator
{
    public Scroller(IGlyph inner) : base(inner) { }
    public override void Draw(Window w) { Inner.Draw(w); w.DrawScrollBars(Bounds()); }
}
Stack them at run time
IGlyph view = new Column();
view = new Border(view, 2);       // wrap once
view = new Scroller(view);        // wrap again — order = visual layering
view.Draw(window);                // scroller draws border draws column
Scroller decorator Border decorator Column the real component wraps wraps All three are IGlyph — the caller can't tell them apart.
Figure 2: Each decorator holds one IGlyph and is an IGlyph. Wrapping order is layering order.
ℹ️ Where .NET does this

Streams are the textbook case: new GZipStream(new BufferedStream(new FileStream(...))) — each wraps a Stream and adds one responsibility. ASP.NET Core middleware is a Decorator-style pipeline around the request.

6 · Look-and-feel → Abstract Factory

Abstract Factory · creational

The requirement. The editor's chrome (buttons, scrollbars, menus) must match the host platform's look-and-feel — Windows widgets on Windows, macOS widgets on macOS — and switching the whole set should be one decision, not a thousand.

The design question. How do we build a UI that is consistent (never a macOS button next to a Windows scrollbar) without new WindowsButton() hard-coded all over the code?

The naive option and why it fails. Scatter if (os == Windows) new WindowsScrollBar() else new MacScrollBar() at every creation site. Add a Linux theme and you edit every site; miss one and you get a mismatched widget. The change request that kills it: "add a third look-and-feel."

The solution. One factory interface with a create-method per widget kind, and one concrete factory per look-and-feel. Client code depends only on the interface, so a whole family swaps by swapping one object. Abstract Factory: provide an interface for creating families of related objects without naming their concrete classes.

GuiFactory.cs — a family of matching widgets
public interface IGuiFactory                 // the Abstract Factory
{
    IButton    CreateButton();
    IScrollBar CreateScrollBar();
    IMenu      CreateMenu();
}

public sealed class WindowsFactory : IGuiFactory
{
    public IButton    CreateButton()    => new WindowsButton();
    public IScrollBar CreateScrollBar() => new WindowsScrollBar();
    public IMenu      CreateMenu()      => new WindowsMenu();
}

public sealed class MacFactory : IGuiFactory
{
    public IButton    CreateButton()    => new MacButton();
    public IScrollBar CreateScrollBar() => new MacScrollBar();
    public IMenu      CreateMenu()      => new MacMenu();
}
The editor is written once, against the interface
public sealed class Editor
{
    private readonly IGuiFactory _ui;      // doesn't know which family
    public Editor(IGuiFactory ui) => _ui = ui;

    public void BuildChrome()
    {
        var save   = _ui.CreateButton();   // guaranteed to match...
        var scroll = _ui.CreateScrollBar();// ...the rest of the family
    }
}

// Chosen ONCE at startup:
IGuiFactory ui = OperatingSystem.IsMacOS() ? new MacFactory() : new WindowsFactory();
var editor = new Editor(ui);
💡 The habitual pair: Abstract Factory + Singleton

You only need one factory instance for the whole app, so it is classically a Singleton (or, in modern C#, a single registration in the dependency-injection container — the DI container is itself a giant, configurable abstract factory). See the collaboration map.

7 · Multiple window systems → Bridge

Bridge · structural

The requirement. Abstract Factory handled the look. But the editor's Window abstraction (with kinds like ApplicationWindow, IconWindow, DialogWindow) must also run over different windowing back-ends (Win32, X11, a headless test surface) that actually push pixels.

The design question. Two things vary independently: the kind of window (application vs dialog…) and the platform that renders it. How do we avoid a class per combination?

❌ Naive: one class per combination
class Win32ApplicationWindow { }
class X11ApplicationWindow   { }
class Win32DialogWindow      { }
class X11DialogWindow        { }
// 3 window kinds × 3 platforms = 9 classes.
// Add a platform → +3. Add a window kind → +3. Multiplicative.
The two dimensions are tangled into one inheritance tree, so their sizes multiply instead of add.
✅ Bridge: two hierarchies, one link
var win = new DialogWindow(new X11WindowImpl());
// 3 kinds + 3 platforms = 6 classes, and they compose.
// Add a platform → +1. Add a kind → +1. Additive.
Abstraction and implementation vary on their own axes and are joined at run time by a reference — the "bridge".

The solution. Split into two hierarchies. The abstraction (Window and its kinds) holds a reference to an implementor (IWindowImpl, one per platform) and delegates the low-level work to it. Bridge: decouple an abstraction from its implementation so the two can vary independently.

Window.cs — abstraction bridged to a platform implementor
public interface IWindowImpl                 // Implementor — the platform
{
    void DeviceRect(double x0, double y0, double x1, double y1);
    void DeviceText(string s, double x, double y);
}
public sealed class X11WindowImpl   : IWindowImpl { /* X11 calls */ }
public sealed class Win32WindowImpl : IWindowImpl { /* Win32 calls */ }

public abstract class Window                 // Abstraction — the window kind
{
    protected readonly IWindowImpl Impl;     // ← the bridge
    protected Window(IWindowImpl impl) => Impl = impl;

    public void DrawRect(Rect r) =>           // high-level op, platform-agnostic
        Impl.DeviceRect(r.X0, r.Y0, r.X1, r.Y1);
    public abstract void DrawContents();
}

public sealed class DialogWindow : Window
{
    public DialogWindow(IWindowImpl impl) : base(impl) { }
    public override void DrawContents() => Impl.DeviceText("[ OK ] [ Cancel ]", 10, 10);
}
ABSTRACTION IMPLEMENTOR Window ApplicationWindow DialogWindow IWindowImpl X11WindowImpl Win32WindowImpl the bridge (has-a) Add a window kind ↓left, or a platform ↓right — the other side is untouched.
Figure 3: Two independent hierarchies joined by one reference. Kinds and platforms add, they don't multiply.
🔀 Bridge vs Abstract Factory — they team up

They're not rivals here. Abstract Factory decides which family of widgets (the look). Bridge decides how any widget reaches the screen (the platform). A Mac look-and-feel can run over more than one rendering back-end. Look and platform are genuinely two axes, so you use two patterns.

8 · Undoable operations → Command + Memento

Command · behavioral

Memento · behavioral

The requirement. Every edit — insert text, delete, format bold, change font — must be undoable and redoable, from a menu, a toolbar or a keyboard shortcut. The slides note the command history is naturally a stack.

The design question. A menu item, a toolbar button and a hotkey all trigger "make bold". How do we invoke the action without the menu knowing how bold works — and, crucially, how do we reverse an arbitrary past action?

The naive option and why it fails. Put the logic in the menu's click handler: OnBoldClick() { /* mutate selection */ }. Now the toolbar duplicates it, the hotkey duplicates it again, and none of them can be undone — there is no object representing "the bold that just happened." The change request that kills it: "add undo/redo."

The solution — Command. Turn each action into an object with Execute() and Unexecute(). Menus, buttons and hotkeys just hold an ICommand and call Execute. A history stack of executed commands gives you undo (pop and Unexecute) and redo. Command: encapsulate a request as an object.

Command.cs — actions as objects, with a history
public interface ICommand
{
    void Execute();
    void Unexecute();     // the inverse — this is what makes undo possible
}

public sealed class InsertTextCommand : ICommand
{
    private readonly Document _doc;
    private readonly string _text;
    private readonly int _at;
    public InsertTextCommand(Document doc, string text, int at)
        => (_doc, _text, _at) = (doc, text, at);

    public void Execute()   => _doc.Insert(_at, _text);
    public void Unexecute() => _doc.Delete(_at, _text.Length);   // exact inverse
}

public sealed class CommandHistory                 // the Invoker + undo stack
{
    private readonly Stack<ICommand> _done = new();
    private readonly Stack<ICommand> _undone = new();

    public void Do(ICommand c) { c.Execute(); _done.Push(c); _undone.Clear(); }
    public void Undo() { if (_done.Count > 0) { var c = _done.Pop(); c.Unexecute(); _undone.Push(c); } }
    public void Redo() { if (_undone.Count > 0) { var c = _undone.Pop(); c.Execute(); _done.Push(c); } }
}

Where Memento comes in. InsertTextCommand can invert itself with a simple delete. But "Format Text" across a selection with mixed existing styles has no tidy inverse — undoing it means restoring the exact prior styling of every run. Rather than compute an inverse, the command snapshots the affected state before it runs, and restores the snapshot on undo. That snapshot is a Memento: capture an object's internal state (without exposing its internals) so it can be restored later.

FormatCommand.cs — Command that undoes via a Memento
// The Memento: opaque to everyone except the Document that made it.
public sealed class DocMemento
{
    internal DocMemento(IReadOnlyList<Run> snapshot) => State = snapshot;
    internal IReadOnlyList<Run> State { get; }   // internal = encapsulated
}

public partial class Document                    // the Originator
{
    public DocMemento Save(Range r)  => new(CopyRuns(r));
    public void Restore(DocMemento m) => ReplaceRuns(m.State);
}

public sealed class FormatCommand : ICommand     // the Command holds the Memento
{
    private readonly Document _doc;
    private readonly Range _range;
    private readonly Style _style;
    private DocMemento _before;                  // saved state for undo

    public FormatCommand(Document doc, Range range, Style style)
        => (_doc, _range, _style) = (doc, range, style);

    public void Execute()
    {
        _before = _doc.Save(_range);             // snapshot BEFORE mutating
        _doc.ApplyStyle(_range, _style);
    }
    public void Unexecute() => _doc.Restore(_before);   // roll back to snapshot
}
DONE (undo stack) Format bold ← top Insert "world" Insert "hello " UNDONE (redo stack) (empty until you undo) Undo → ← Redo A new edit clears the redo stack — you can't redo into an abandoned timeline.
Figure 4: Two stacks give you unlimited undo/redo. Each box is a Command; a Format command also carries a Memento of the pre-edit state.
ℹ️ Where .NET does this

ICommand is a first-class citizen in WPF/MAUI data binding — a button's Command property is this pattern. Entity Framework's change tracker snapshots entity state to support rollback — a Memento in spirit.

9 · Spell-check & hyphenation → Iterator + Visitor

Iterator · behavioral

Visitor · behavioral

The requirement. Analyses must sweep the whole document: spell-checking gathers words and flags misspellings; hyphenation finds break points; word-count tallies; find-and-replace scans. New analyses will keep being added.

The design question — two of them. (a) How do we walk the Composite tree in reading order without every analysis re-implementing the recursion? (b) How do we add a new analysis (say, a grammar checker) without editing every glyph class to add a GrammarCheck() method?

Part A — Iterator. Provide one way to traverse the tree, separate from the tree's structure. Iterator: access the elements of an aggregate sequentially without exposing its representation. In C# this is simply IEnumerable<IGlyph> / foreach — the language bakes Iterator in.

Traversal.cs — one depth-first walk, reused by all analyses
public static class GlyphTree
{
    // yield return = a lazy Iterator, courtesy of the language.
    public static IEnumerable<IGlyph> PreOrder(IGlyph root)
    {
        yield return root;
        foreach (var child in root.Children())
            foreach (var g in PreOrder(child))
                yield return g;
    }
}

// Any analysis just enumerates — no recursion of its own:
foreach (var g in GlyphTree.PreOrder(document)) { /* look at g */ }

Part B — Visitor. The naive fix for "add an analysis" is to add a method to IGlyph: SpellCheck(), then Hyphenate(), then GrammarCheck()… Every new analysis edits every glyph class — the change request "add a readability score" touches Character, Image, Row, Table, everything. That is the Open/Closed problem again, on the element hierarchy.

Instead, put each analysis in its own object and let glyphs accept it. Visitor: represent an operation to perform on the elements of a structure, so you can add new operations without changing the element classes.

// Glyphs gain ONE stable method: Accept. It never changes again.
public interface IGlyph            // (added to the interface from §1)
{
    void Accept(IGlyphVisitor v);
    IEnumerable<IGlyph> Children();
    // ...Draw, Bounds, etc...
}

public sealed partial class Character : IGlyph
{
    public char Symbol { get; }
    public void Accept(IGlyphVisitor v) => v.VisitCharacter(this);
}
public sealed partial class Image : IGlyph
{
    public void Accept(IGlyphVisitor v) => v.VisitImage(this);
}
public interface IGlyphVisitor
{
    void VisitCharacter(Character c);
    void VisitImage(Image img);
}

// A whole new analysis = a whole new class. No glyph is touched.
public sealed class SpellCheckVisitor : IGlyphVisitor
{
    // A HashSet, not a Dictionary<K,V>: case-insensitive membership tests.
    private static readonly HashSet<string> _dictionary =
        new(StringComparer.OrdinalIgnoreCase) { "hello", "world", "the", "cat" };

    private readonly System.Text.StringBuilder _word = new();
    public readonly List<string> Misspelled = new();

    public void VisitCharacter(Character c)
    {
        if (char.IsLetter(c.Symbol)) _word.Append(c.Symbol);
        else FinishWord();
    }
    public void VisitImage(Image img) => FinishWord();   // images break words

    private void FinishWord()
    {
        if (_word.Length > 0 && !_dictionary.Contains(_word.ToString()))
            Misspelled.Add(_word.ToString());
        _word.Clear();
    }
}
// Iterator supplies the walk; Visitor supplies the operation.
var spell = new SpellCheckVisitor();
foreach (var g in GlyphTree.PreOrder(document))
    g.Accept(spell);

Console.WriteLine($"Misspelled: {string.Join(", ", spell.Misspelled)}");

// Add hyphenation later: write HyphenationVisitor, change NOTHING else.
🔀 The Visitor trade-off (know this)

Visitor makes adding operations easy but adding element types hard: a new Table glyph forces a new VisitTable method on every visitor. So Visitor pays off when the element hierarchy is stable but the operations keep growing — exactly the document-analysis situation. If your elements churn more than your operations, don't use Visitor.

ℹ️ Where .NET does this

IEnumerator<T> and foreach are the Iterator pattern — you use it daily. The Roslyn compiler and System.Linq.Expressions both expose ExpressionVisitor to walk and rewrite syntax/expression trees.

The whole system, together

Nine requirements, nine pattern decisions — and none of them stands alone. Here they are cooperating around the Document core. Notice the colours: creational patterns make things, structural patterns compose them, behavioral patterns move behaviour between them.

Document Core Composite tree of Glyphs IGlyph Composite char/image/table uniform Flyweight share glyph objects Decorator borders · scrollbars Bridge window systems Strategy line-breaking Abstract Factory look-and-feel Iterator + Visitor spell-check · hyphenate Builder assemble the document Command + Memento undo / redo
Figure 5: The fused architecture. Every pattern touches the same Document/IGlyph core — that shared interface is the glue that lets nine patterns cooperate. Green = creational, blue = structural, purple = behavioral.
🧠 The one idea to keep

Patterns fuse through shared abstractions. Composite, Decorator and Flyweight all speak IGlyph; Command and Memento both revolve around Document; Visitor and Iterator both traverse the same tree. Design the core interface well and the patterns snap onto it like accessories — design it badly and every pattern needs an adapter to reach the next.

Interactive: the editor design explorer

Click any component of the editor's architecture. It highlights, and the panel tells you which pattern it uses and why that requirement led there. This is the "reason to the pattern" muscle you're building.

Try it: click a component to see its pattern
Editor Window Bridge Widget Toolkit Abstract Factory Border / Scroller Decorator Document (Glyph tree) Composite + Flyweight Compositor Strategy Doc Builder Builder Command History Command + Memento Analysis Pass Iterator + Visitor
Document (Glyph tree)
Composite Flyweight
The document is a tree where a single character and a whole table are treated uniformly (Composite), and identical characters share one object to keep memory flat (Flyweight). Everything else in the editor plugs into this core.
What to notice: every component points back to the Document core. The patterns aren't independent gadgets — they're all arranged around one shared abstraction.

And a tiny working editor, to feel three of the patterns run at once — type text (Command), toggle a border (Decorator), and undo down the command history (Command + Memento):

Try it: a 3-pattern micro-editor
— the document —
What to notice: each button creates a command object pushed onto a history stack; Undo pops the top and calls its inverse. The border isn't a new document type — it's a Decorator toggled on the same content. Bold snapshots the text style (a Memento) so undo restores it.

Pattern collaboration map

Beyond this one editor, certain pattern pairings recur across almost every system. Learn them as idioms — when you see one, the other is often nearby.

PairingHow they collaborateSeen in our editor?
Composite + Iterator + VisitorComposite builds the tree; Iterator walks it; Visitor performs pluggable operations during the walk.Yes — document tree, traversal, spell-check.
Composite + DecoratorThey share one component interface on purpose, so decorators nest into the composite tree like any node.Yes — borders/scrollbars wrap glyphs.
Composite + FlyweightThe tree holds lightweight nodes that reference shared flyweights for their heavy, repeated data.Yes — glyph tree + shared characters.
Command + MementoA command snapshots pre-execution state as a memento so it can undo actions with no easy inverse.Yes — Format command's undo.
Abstract Factory + SingletonUsually one factory instance exists app-wide; it's held as a Singleton (or a single DI registration).Yes — one GUI factory chosen at startup.
Abstract Factory + Factory MethodEach create-method of the abstract factory is frequently a Factory Method in the concrete factory.Implicitly.
Bridge + Abstract FactoryThe factory can create and pair the right implementor with the abstraction (look × platform).Yes — widgets over window systems.
Builder + CompositeBuilder's job is often precisely to assemble a Composite tree step by step.Yes — DocumentBuilder builds glyphs.
Strategy + Template MethodA template method fixes the skeleton; strategies fill the varying step (here, line-breaking).Compositor as a strategy.
Composite Decorator Flyweight Iterator Visitor Builder Abstract Factory Singleton
Figure 6: The habitual pairings as a graph. Composite is the social hub of this design — most structural and traversal patterns connect through it.

Check yourself

Each question is a mini design brief. Reason from the requirement to the pattern — that's the whole skill of this chapter.

💡 Where to go next

Confused which pattern is which? Chapter 12 — Confusable Patterns lines up the tricky look-alikes (Decorator vs Proxy vs Adapter, Strategy vs State, Factory Method vs Abstract Factory) side by side. Then Chapter 13 tests the whole module with scenario questions like these.