CHAPTER 5 Β· DESIGN PATTERNS

Behavioral Design Patterns

Creational patterns are about making objects; structural patterns are about composing them. Behavioral patterns are about the third thing: how objects talk to each other and divide up responsibility at run-time. They tame the messy control flow β€” who calls whom, in what order, and what happens when state changes β€” that turns readable code into spaghetti.

🧠 Why a whole chapter on "behavior"?

The Gang of Four split behavioral patterns into two families. Class patterns (Template Method, Interpreter) use inheritance to spread behavior across a class hierarchy. Object patterns use composition instead: some encapsulate communication between objects (Mediator, Chain of Responsibility, Observer), others wrap a piece of behavior in its own object and delegate to it (Strategy, Command, State, Visitor, Iterator). Keep that split in mind β€” it's the single best predictor of how each pattern is wired.

ℹ️ How this page is organised

Eleven patterns, each with the same rhythm: a real-world analogy β†’ the naive code and the exact change request that breaks it β†’ the pattern's fix in C# β†’ a UML diagram β†’ a live demo you can click β†’ when (and when not) to use it β†’ where .NET itself uses it β†’ an "easily confused with…" note. The five that students mix up most (Strategy/State, Command/Strategy, Observer/Mediator, Template Method/Strategy, and Visitor's double dispatch) get extra attention and point to Chapter 12 for full side-by-sides.

Chain of Responsibility behavioral

Pass a request down a line of handlers until one deals with it

Chain of Responsibility (CoR) lets you send a request into a pipeline of handler objects. Each handler either processes the request, passes it to the next handler, or both β€” and the sender never needs to know which handler will actually do the work.

🌍 Real-world analogy

Think of tech-support escalation. You call the helpline: a level-1 agent tries first. Can't fix it? They escalate to level-2. Still stuck? It goes to engineering. You (the caller) don't pick who solves your problem β€” you drop it into the chain and it walks up until someone with the right authority handles it. If nobody can, it comes back unresolved.

The problem

Imagine an online ordering system. You start with one rule: only authenticated users can create orders. Simple. Then the requests keep coming:

The naive approach crams every check into one method. Here's what it becomes:

❌ Naive: one giant method of nested checks
public class OrderController
{
    public string CreateOrder(Request req)
    {
        // authentication
        if (!IsAuthenticated(req))
            return "401 Unauthorized";

        // rate limiting
        if (TooManyAttempts(req))
            return "429 Too Many Requests";

        // sanitisation
        if (!IsClean(req))
            return "400 Bad Request";

        // cache
        if (Cache.TryGet(req, out var cached))
            return cached;

        return Process(req); // the real work, buried at the bottom
    }
}
Every new rule = edit this method. The checks are tangled, can't be reordered, and can't be reused on another endpoint without copy-paste. Violates the Open/Closed Principle.
βœ… With Chain of Responsibility
var chain = new AuthHandler();
chain.SetNext(new RateLimitHandler())
     .SetNext(new SanitizeHandler())
     .SetNext(new CacheHandler());

// One call. Each link decides to handle,
// pass on, or stop the chain.
string result = chain.Handle(req);
Each check is its own class. Reorder by re-linking. Add a check = add a class, touch nothing existing. Reuse the same links on any endpoint.
⚠️ The change request that kills the naive version

"Marketing wants the cache checked before rate-limiting on the read-only search endpoint, but after it on checkout." In the giant-method version that means duplicating the whole method with the lines reshuffled. With CoR you just build a different chain from the same handler classes.

The solution

The insight: turn each behavior into a stand-alone handler object, and give every handler a reference to the next one. A base class holds the boilerplate ("call the next handler if there is one"); concrete handlers just decide whether to act and whether to continue. The source materials build this exact pattern with HTTP header handlers β€” we'll keep that domain in C#.

HandlerChain.cs
// The Handler: declares the interface + the "pass it on" boilerplate.
// We use an abstract class (not a pure interface) because the
// do-next logic is shared by every handler β€” no point duplicating it.
public abstract class HandlerChain
{
    private HandlerChain? _next;

    // Fluent linking: returns the handler we just attached so
    // calls can be chained: a.SetNext(b).SetNext(c);
    public HandlerChain SetNext(HandlerChain next)
    {
        _next = next;
        return next;
    }

    // Each concrete handler implements this.
    public abstract string AddHeader(string input);

    // Shared: forward to the next link, or end the chain.
    protected string DoNext(string input)
        => _next is null ? input : _next.AddHeader(input);
}
Handlers.cs
public class AuthenticationHeader : HandlerChain
{
    private readonly string _token;
    public AuthenticationHeader(string token) => _token = token;

    public override string AddHeader(string input)
    {
        var h = $"{input}\nAuthorization: {_token}";
        return DoNext(h);                // process, then pass on
    }
}

public class ContentTypeHeader : HandlerChain
{
    private readonly string _contentType;
    public ContentTypeHeader(string ct) => _contentType = ct;

    public override string AddHeader(string input)
        => DoNext($"{input}\nContent-Type: {_contentType}");
}

public class BodyPayloadHeader : HandlerChain
{
    private readonly string _body;
    public BodyPayloadHeader(string body) => _body = body;

    public override string AddHeader(string input)
        => DoNext($"{input}\n{_body}");
}
Program.cs
var auth = new AuthenticationHeader("123456");
auth.SetNext(new ContentTypeHeader("application/json"))
    .SetNext(new BodyPayloadHeader("{ \"user\": \"john\" }"));

// Enter at the top of the chain:
string message = auth.AddHeader("POST /orders HTTP/1.1");
Console.WriteLine(message);

// Enter partway down β€” auth is simply skipped:
// contentTypeHeader.AddHeader("GET /health HTTP/1.1");
πŸ’‘ A handler can STOP the chain

The power move of CoR is that a handler may choose not to call DoNext. An AuthHandler that fails authentication just returns "401" and the request never reaches sanitisation or processing. That short-circuit is exactly what the naive nested-if version was faking.

HandlerChain + SetNext(h) + AddHeader(x) next β–Έ (0..1) AuthenticationHeader handles + forwards ContentTypeHeader handles + forwards BodyPayloadHeader may stop chain Concrete handlers all implement AddHeader; base class owns the "next" link
Figure: CoR β€” one abstract Handler with a self-reference (next), many concrete handlers that inherit the forwarding boilerplate.
Try it: support-ticket escalation

Pick a ticket severity and fire it into the chain. Watch it walk L1 β†’ L2 β†’ Engineering β†’ Manager. Each handler only deals with problems at or below its authority; otherwise it escalates. Some tickets fall off the end unhandled.

What to notice: the client just calls chain.Handle(ticket) once. It never names L2 or Engineering. Reordering or inserting a handler wouldn't change this call at all β€” and severity 5 shows the honest "nobody handled it" outcome the pattern must account for.

When to use it β€” and when not to

βœ… Reach for CoR when…
  • More than one object can handle a request and which one isn't known in advance β€” it should be decided at run-time.
  • You want to issue a request without hard-coding the receiver.
  • The set of handlers should be configurable dynamically (per-endpoint pipelines, middleware).
⚠️ Costs & drawbacks
  • No guarantee of handling. A request can fall off the end unprocessed β€” you must design for that.
  • Debugging is harder: control flow hops object to object, hard to follow at run-time.
  • Latency stacks up if the chain is long and every link runs.
ℹ️ Where .NET uses it

ASP.NET Core middleware is Chain of Responsibility at industrial scale: each middleware component gets the HttpContext, does its bit (auth, logging, CORS, compression), and calls await next(context) β€” or short-circuits the response. DelegatingHandler in HttpClient's message-handler pipeline is the same idea for outbound requests. WPF/WinForms event bubbling up the visual tree is a CoR variant too.

πŸ”€ Easily confused

CoR pairs naturally with Composite β€” a component's parent can act as its successor, so unhandled requests bubble up the tree. Don't confuse CoR with Command: Command wraps one request as an object to be stored/queued/undone; CoR is about routing a request through several possible handlers. See Chapter 12 for the full comparison.

Command behavioral

Wrap a request as an object so you can store, queue, and undo it

Command turns "call this method with these arguments" into a first-class object. Once a request is an object, you can put it in a list, pass it around, log it, schedule it β€” and, crucially, give it an Undo.

🌍 Real-world analogy

In a restaurant you don't walk into the kitchen and cook. You write your order on a slip and hand it to the waiter. The slip is a command: it holds everything needed to make the meal. It sits in a queue until the chef is free, the chef can work through slips in order, and the slip decouples you (who wants food) from the chef (who makes it). You never talk to the chef directly.

The problem

You're building a text editor's toolbar. Every button β€” Copy, Paste, Bold, Undo β€” looks the same but does something different. The naive fix is a Button subclass per action:

❌ Naive: a subclass per button, wired to business logic
class CopyButton : Button
{
    public override void OnClick()
    {
        // GUI code reaches straight into the editor
        Editor.Instance.Selection.CopyToClipboard();
    }
}
class PasteButton : Button { /* more of the same */ }
class BoldButton : Button  { /* ...and again... */ }
// One class per action. Menu items? Shortcuts?
// Duplicate the SAME logic yet again.
The GUI is now welded to volatile business logic. The same "copy" action lives in a button, a menu, and a keyboard shortcut β€” three copies. Undo is a nightmare: there's no single object representing "what just happened".
βœ… With Command
// One reusable Button. Hand it any command.
var copy  = new CopyCommand(editor);
var paste = new PasteCommand(editor);

toolbarButton.Command = copy;   // button
menuItem.Command      = copy;   // menu β€” SAME object
shortcutCtrlC.Command = copy;   // shortcut

// Trigger uniformly; nobody knows the details:
button.Command.Execute();
One button class, reused everywhere. The action is an object you can share across UI surfaces, push onto an undo stack, or queue for later.
⚠️ The change request that kills the naive version

"Add multi-level undo/redo." With subclasses-per-button there's no object that captures "the paste that happened at 10:42 with this text", so there's nothing to reverse. Command makes each action an object that can store the state needed to undo itself β€” undo becomes a stack of command objects.

The solution

The insight: extract the receiver, the method, and the arguments into a Command object with a single Execute() (and, for undo, an Undo()). The invoker (button, queue, scheduler) holds a command and triggers it without knowing what it does. The receiver (the editor) does the real work. The slide materials use an order-processing queue; we'll show both the queue idea and undo.

Command.cs
// The Command interface: one method to run, one to reverse.
public interface ICommand
{
    void Execute();
    void Undo();
}

// The Receiver: holds the real business logic / state.
public class TextDocument
{
    public string Text { get; private set; } = "";
    public void Append(string s) => Text += s;
    public void RemoveLast(int count) =>
        Text = Text[..Math.Max(0, Text.Length - count)];
}
TypeCommand.cs
// A Concrete Command remembers what it did so it can undo it.
public class TypeCommand : ICommand
{
    private readonly TextDocument _doc;
    private readonly string _text;

    public TypeCommand(TextDocument doc, string text)
    {
        _doc = doc;
        _text = text;
    }

    public void Execute() => _doc.Append(_text);
    public void Undo()    => _doc.RemoveLast(_text.Length);
}
Editor.cs (invoker)
public class Editor
{
    private readonly Stack<ICommand> _done = new();
    private readonly Stack<ICommand> _undone = new();

    public void Run(ICommand cmd)
    {
        cmd.Execute();
        _done.Push(cmd);
        _undone.Clear();      // new action invalidates redo
    }

    public void Undo()
    {
        if (_done.Count == 0) return;
        var cmd = _done.Pop();
        cmd.Undo();
        _undone.Push(cmd);
    }

    public void Redo()
    {
        if (_undone.Count == 0) return;
        var cmd = _undone.Pop();
        cmd.Execute();
        _done.Push(cmd);
    }
}
Deep dive: the slides' order-queue version (parameterise & defer execution)

The lecture example queues commands instead of undoing them β€” this shows the "specify, queue and execute requests at different times" use case:

public interface ICommand { void Execute(); }

public class OrderAddCommand : ICommand
{
    private readonly int _id;
    public OrderAddCommand(int id) => _id = id;
    public void Execute() => Console.WriteLine($"Adding order {_id}");
}

public class CommandProcessor            // invoker with a queue
{
    private readonly Queue<ICommand> _queue = new();
    public void Add(ICommand c) => _queue.Enqueue(c);
    public void ProcessAll()
    {
        while (_queue.Count > 0) _queue.Dequeue().Execute();
    }
}

Because a command is just an object, the processor can run them later, in order, on another thread, or retry after a crash β€” none of which is possible when the action is a hard-coded method call.

Editor Invoker (holds cmd) Β«interfaceΒ» ICommand TypeCommand Execute()/Undo() TextDocument Receiver (real work) triggers delegates to
Figure: The invoker knows only ICommand. The concrete command holds a reference to the receiver and calls the real methods on it.
Try it: text editor with undo / redo

Every button click creates a command object and runs it through the invoker. The two stacks below are the actual undo/redo history β€” watch commands move between them.

Document
 
Stacks
undo: []   redo: []
What to notice: undo isn't magic β€” each command stored what it needed to reverse itself. A new action clears the redo stack, exactly like real editors. The buttons don't know how "bold" works; they just push a command object.

When to use it β€” and when not to

βœ… Reach for Command when…
  • You want to parameterise objects with an action β€” commands are the OO replacement for callbacks/function pointers.
  • You need to queue, schedule, or execute requests at different times.
  • You need undo/redo or a change log that can be replayed after a crash.
⚠️ Costs & drawbacks
  • A class (or lambda) per action β€” lots of tiny types for simple apps.
  • Undo means every command must correctly capture and restore state (often via Memento).
ℹ️ Where .NET uses it

WPF's ICommand (with RelayCommand/DelegateCommand) is Command by name β€” it's how MVVM binds buttons to view-model actions. MediatR's request/handler model is Command-flavoured. And any Action/Func<T> delegate you pass to Task.Run or an event is a lightweight command β€” C# lets you use a lambda where GoF used a class.

πŸ”€ Easily confused: Command vs Strategy

Both wrap behavior in an object with one method. The difference is intent. A Strategy is a plug-in algorithm β€” "how to do X" (sort this way vs that way); you swap it to change how a single job is done. A Command is a request β€” "do X to Y, and maybe let me undo it later"; it bundles a receiver and is meant to be stored, queued, and reversed. Rule of thumb: if you'd put it on an undo stack or a queue, it's a Command. Full breakdown in Chapter 12. Related: a Composite of commands makes a macro; a Memento stores the state a command needs to undo.

Iterator behavioral

Walk a collection's elements without exposing how it's stored

Iterator gives you a uniform way to step through the elements of a collection β€” a list, a tree, a graph β€” without the client ever seeing the internal structure. Traversal logic moves out of the collection and into a separate iterator object.

🌍 Real-world analogy

A TV remote's channel-up button. You press "next" and get the next channel. You have no idea whether channels are stored in an array, a linked list, or fetched over the air β€” and you don't care. The remote is an iterator: it knows the current position and how to advance; you just ask for "next".

The problem

Collections come in many shapes: lists, stacks, trees, graphs. Looping over a list is trivial. But how do you traverse a tree β€” depth-first? breadth-first? And what if the client wants to loop over the same collection twice at once? If the traversal cursor lives inside the collection, you can only have one active traversal, and every client is forced to learn the internal layout.

❌ Naive: expose the internals and make the client walk them
public class SocialGraph
{
    public Node[] Nodes;          // client must know it's an array
    public int[][] AdjacencyList; // ...and how edges are stored
}

// Every client re-implements traversal AND depends on the layout.
// Change the array to a Dictionary and every loop breaks.
The collection leaks its representation. Two simultaneous walks share one implicit cursor. Switching the storage type is a breaking change everywhere.

The solution

The insight: extract traversal into an Iterator object that keeps its own position. The collection just hands out iterators; each iterator independently tracks where it is, so several can run over the same data at once. The notebook example iterates a word collection alphabetically (and in reverse) β€” we'll translate that, then show the idiomatic C# way.

Iterator.cs
// The Iterator interface: the two questions every traversal answers.
public interface IIterator<T>
{
    bool HasNext();
    T Next();
}

// The Collection interface: hands out iterators, hides its guts.
public interface IWordCollection
{
    IIterator<string> GetIterator();
    IIterator<string> GetReverseIterator();
}
AlphabeticalIterator.cs
public class AlphabeticalIterator : IIterator<string>
{
    private readonly List<string> _items;
    private readonly bool _reverse;
    private int _pos;

    public AlphabeticalIterator(IEnumerable<string> words, bool reverse = false)
    {
        _items = words.OrderBy(w => w).ToList();
        _reverse = reverse;
        _pos = reverse ? _items.Count - 1 : 0;
    }

    public bool HasNext() => _reverse ? _pos >= 0 : _pos < _items.Count;

    public string Next()
    {
        var value = _items[_pos];
        _pos += _reverse ? -1 : 1;   // iterator owns the cursor
        return value;
    }
}

public class WordCollection : IWordCollection
{
    private readonly List<string> _words;
    public WordCollection(IEnumerable<string> words) => _words = words.ToList();

    public IIterator<string> GetIterator() => new AlphabeticalIterator(_words);
    public IIterator<string> GetReverseIterator() => new AlphabeticalIterator(_words, true);
}
The C# way: IEnumerable + yield
// In C# you rarely hand-roll an iterator: IEnumerable<T> is the
// aggregate, IEnumerator<T> is the Iterator, and `yield return` writes it for you.
public class Playlist : IEnumerable<string>
{
    private readonly List<string> _songs = new();
    public void Add(string s) => _songs.Add(s);

    public IEnumerator<string> GetEnumerator()
    {
        foreach (var song in _songs)
            yield return song;      // compiler builds the iterator
    }
    System.Collections.IEnumerator
        System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
}

// Client β€” never sees the List inside:
foreach (var song in new Playlist()) Console.WriteLine(song);
πŸ’‘ Translating from the slides

Python's iterators use __next__ and raise StopIteration; the notebook rolls a manual has_next()/next(). In C#, foreach is built on IEnumerator<T>'s MoveNext()/Current, and yield return generates that state machine so you almost never write HasNext/Next by hand. Learn the manual form to understand the pattern; use yield in real code.

Β«interfaceΒ» IWordCollection Β«interfaceΒ» IIterator<T> WordCollection GetIterator() AlphabeticalIterator owns position creates β–Έ Several iterators can walk one collection independently
Figure: The collection is a factory for iterators; each iterator carries its own cursor.
Try it: step through a playlist iterator

Grab an iterator, then press Next to advance. The client code below only ever calls HasNext() and Next() β€” it has no idea the songs live in a list, sorted on the fly.

What to notice: two different traversals (forward/reverse) over the same collection, each with its own position. The client never touches the underlying list β€” swap it for a tree and this loop wouldn't change.

When to use it β€” and when not to

βœ… Reach for Iterator when…
  • You want to access a collection's contents without exposing its representation.
  • You need to support multiple simultaneous or multiple kinds of traversal (friends vs colleagues, DFS vs BFS).
  • You want one uniform loop interface across different structures (polymorphic iteration).
⚠️ Costs & drawbacks
  • Overkill for a plain array you fully control β€” just loop.
  • Iterators can become stale/invalid if the collection is modified mid-traversal.
ℹ️ Where .NET uses it

This is the canonical .NET pattern: IEnumerable<T> is the aggregate and IEnumerator<T> is the Iterator, and every foreach, every LINQ query (Where, Select are lazy iterators), and every yield return is built on it. Related patterns: iterators pair with Composite for recursive walks, use a Factory Method to create the right iterator, and can use a Memento to snapshot iteration state.

πŸ”€ Easily confused

Iterator vs Visitor: an Iterator traverses a structure and hands you elements one at a time; a Visitor performs an operation on each element. They combine well β€” iterate to visit. See Chapter 12.

Mediator behavioral

Route all communication through one hub instead of a tangle of direct links

Mediator defines an object that encapsulates how a set of objects interact. Components stop referring to each other directly; they talk only to the mediator, which decides who needs to know what. This collapses an NΒ² web of connections into a clean star.

🌍 Real-world analogy

Air-traffic control. Pilots don't radio each other to negotiate who lands first β€” that would be chaos with a dozen planes. Every pilot talks only to the control tower, and the tower coordinates everyone. Remove the tower and you'd need every plane aware of every other plane. The tower is the mediator.

The problem

OO design encourages spreading behavior across many small objects β€” good. But those objects then need to coordinate, and in the worst case every object ends up knowing about every other object. In a chat room with N users, direct messaging means each user holds references to the other Nβˆ’1 users: that's NΓ—(Nβˆ’1) links, and adding a user means touching every existing one.

Without mediator: NΒ² links A B C D With mediator: N links Mediator A B C D
Figure: The mediator turns a quadratic mesh of dependencies into a linear star.

The solution

The insight: cut all direct links between components; each one notifies a single mediator, which routes to whoever should react. Components depend only on the mediator interface, not on each other, so you can add, remove, or rewire them without editing their siblings. The notebook models a chat room β€” we'll keep it.

ChatMediator.cs
public interface IChatMediator
{
    void Register(ChatUser user);
    void Send(string message, ChatUser sender);
}

// Concrete Mediator: knows all users, does the routing.
public class ChatRoom : IChatMediator
{
    private readonly List<ChatUser> _users = new();

    public void Register(ChatUser user)
    {
        _users.Add(user);
        user.SetMediator(this);
    }

    public void Send(string message, ChatUser sender)
    {
        foreach (var u in _users)
            if (u != sender)          // don't echo to the sender
                u.Receive(message, sender.Name);
    }
}
ChatUser.cs
// A Colleague talks ONLY to the mediator β€” never to other users.
public class ChatUser
{
    public string Name { get; }
    private IChatMediator? _mediator;
    public ChatUser(string name) => Name = name;

    public void SetMediator(IChatMediator m) => _mediator = m;

    public void Send(string message)
    {
        Console.WriteLine($"{Name} sends: {message}");
        _mediator?.Send(message, this);   // hand it to the hub
    }

    public void Receive(string message, string from)
        => Console.WriteLine($"  {Name} got from {from}: {message}");
}
Program.cs
var room = new ChatRoom();
var alice = new ChatUser("Alice");
var bob   = new ChatUser("Bob");
var carol = new ChatUser("Carol");

room.Register(alice);
room.Register(bob);
room.Register(carol);

carol.Send("Hi everyone!");  // routed to Alice & Bob via the room
// Carol has no reference to Alice or Bob at all.
Try it: chat room routed through a mediator

Add participants, then have one send a message. Every message flows sender β†’ mediator β†’ everyone else. No participant ever holds a reference to another.

What to notice: adding the 5th participant added one link (to the mediator), not four. The routing logic lives in one place β€” change "broadcast to all" into "private message" by editing only the mediator.

When to use it β€” and when not to

βœ… Reach for Mediator when…
  • A set of objects communicate in complex, tangled ways that are hard to follow.
  • An object is hard to reuse because it's wired to many others.
  • Behavior spread across classes should be customisable without a swarm of subclasses.
⚠️ Costs & drawbacks
  • The mediator can swell into a God Object that knows everything β€” you moved the complexity, not removed it.
  • All traffic funnels through one point; it can become a bottleneck or a single point of failure.
ℹ️ Where .NET uses it

The MediatR library (hugely popular in ASP.NET Core) is Mediator by name: controllers Send a request to the mediator, which finds the handler β€” controllers and handlers never reference each other. WPF/WinForms form controllers that coordinate a dialog's widgets are classic mediators. SignalR hubs route client messages centrally.

πŸ”€ Easily confused: Mediator vs Observer vs Facade vs Pub/Sub

All four reduce coupling, but differently. The real differentiator is what each coordinates: Observer broadcasts one subject's change out to many subscribers; Mediator centralises the coordination between many peers (colleagues) so they never reference each other directly. Directionality is a useful clue, not a law: Observer is typically one-to-many and one-directional (subscribers don't talk back), and Mediator is typically many-to-many with two-way traffic through the hub β€” but a mediator can be largely one-way, and observers can push updates back through another subject. Facade just simplifies access to a subsystem that doesn't call back; Pub/Sub (message bus) is Observer with a broker between publisher and subscriber so neither knows the other. Colleagues often notify a mediator using Observer. Full grid in Chapter 12.

Memento behavioral

Snapshot and restore an object's state without breaking encapsulation

Memento (aka Snapshot) captures an object's internal state into a separate object so it can be restored later β€” without exposing that state to the outside world. It's the pattern behind "undo" and "checkpoint".

🌍 Real-world analogy

A video-game save file. When you save, the game writes out your exact state β€” health, position, inventory. Later you load and you're back precisely where you were. You never got to read or hand-edit the save's internals; only the game knows how to make one and how to read it back. That opaque save is a memento.

The problem

You want undo in a text editor. Before each operation you snapshot the editor's state; on undo you restore the latest snapshot. The obvious approach: let some external "history" object read all the editor's fields and copy them. But real objects hide their state in private fields β€” and if you make everything public so the history can read it, you've destroyed encapsulation. Now every class in the app can poke the editor's guts, and any refactor of those fields breaks the history code.

❌ Naive: expose internals so an outsider can copy them
public class Editor
{
    public string Content;     // had to make it public...
    public int CursorPos;      // ...and this...
    public string FontName;    // ...and this, just so History can read them
}

public class History
{
    public void Save(Editor e) { /* copies e.Content, e.CursorPos... */ }
}
// Encapsulation gone. Every field is now part of the public API.
To let History snapshot the state, the editor had to leak all its fields. Change a field's type and History breaks. Any code can now corrupt the editor.

The solution

The insight: let the object snapshot itself. The originator (the editor) has full access to its own private state, so it produces a memento object holding a copy. The caretaker (the history) stores mementos but can't read inside them β€” it only holds and hands them back. State stays private; encapsulation survives.

Memento.cs
// The Memento: an immutable snapshot. `internal` keeps Content hidden
// from callers in OTHER assemblies. Be honest about what that buys you:
// `internal` means EVERY type in THIS assembly can read Content, not
// only the Originator. It's a pragmatic guard β€” fine when the Editor
// lives in its own assembly β€” not true originator-only access. For that,
// use the private-nested-class variant shown below.
public sealed class EditorMemento
{
    // Internal β†’ readable by any code in the SAME assembly.
    internal string Content { get; }
    public DateTime SavedAt { get; }   // metadata IS public

    internal EditorMemento(string content)
    {
        Content = content;
        SavedAt = DateTime.Now;
    }
}
Editor.cs (originator)
public class Editor
{
    private string _content = "";      // stays private!
    public void Type(string s) => _content += s;
    public string Read() => _content;

    // Make a snapshot of my own private state.
    public EditorMemento Save() => new(_content);

    // Restore from a snapshot I previously produced.
    public void Restore(EditorMemento m) => _content = m.Content;
}
History.cs + usage
// The Caretaker: keeps mementos but never looks inside them.
public class History
{
    private readonly Stack<EditorMemento> _snapshots = new();
    public void Push(EditorMemento m) => _snapshots.Push(m);
    public EditorMemento? Pop() => _snapshots.Count > 0 ? _snapshots.Pop() : null;
}

var editor = new Editor();
var history = new History();

editor.Type("Hello");
history.Push(editor.Save());   // checkpoint
editor.Type(", world");
Console.WriteLine(editor.Read());   // Hello, world

var snap = history.Pop();
if (snap is not null) editor.Restore(snap);
Console.WriteLine(editor.Read());   // Hello   (undone)
πŸ’‘ The three roles, precisely

Originator = the object whose state you save (Editor). Memento = the opaque snapshot. Caretaker = whoever asks for and stores snapshots but must not read them (History). What preserves encapsulation is keeping the memento's payload hidden from the caretaker. The internal modifier above does that across assemblies (a pragmatic guard); for true originator-only access β€” where literally no other type can read the payload β€” make the memento a private nested class inside the Originator, as shown next.

The strict encapsulation variant: memento as a private nested class

Since internal only limits access to the assembly, the textbook way to make a memento readable by nothing but the Originator is to declare it as a private nested type and expose it to the outside world only through an empty public marker interface. The caretaker holds the interface (seeing metadata at most); only the Originator can see the concrete type and its payload.

public class Editor
{
    private string _content = "";
    public void Type(string s) => _content += s;
    public string Read() => _content;

    // Public, opaque marker β€” the ONLY thing the caretaker can name.
    public interface IMemento { DateTime SavedAt { get; } }

    // PRIVATE + NESTED: no type outside Editor can even name Snapshot,
    // so Content is genuinely readable only by the Editor itself.
    private sealed class Snapshot : IMemento
    {
        public string Content { get; }
        public DateTime SavedAt { get; } = DateTime.Now;
        public Snapshot(string content) => Content = content;
    }

    public IMemento Save() => new Snapshot(_content);
    public void Restore(IMemento m) => _content = ((Snapshot)m).Content;
}

The cast (Snapshot)m only compiles inside Editor, because Snapshot is invisible everywhere else. That is the real "originator-only" guarantee that internal can only approximate.

Try it: editor with save-snapshot / restore

Type into the document, then Save snapshot at any point. The caretaker's history list fills up. Restore rolls the document back β€” but the caretaker only ever saw opaque tokens with timestamps.

Document
 
Caretaker history (opaque)
β€” empty β€”
What to notice: the caretaker list shows only timestamps, not content β€” it literally cannot read the saved text. The originator is the only object that can produce and re-open a snapshot.

When to use it β€” and when not to

βœ… Reach for Memento when…
  • You need to save/restore an object's state (undo, checkpoints, transactions, "cancel changes").
  • Exposing the state directly would break encapsulation.
⚠️ Costs & drawbacks
  • Snapshots can be memory-heavy if state is large or you keep a long history.
  • The caretaker must manage the lifetime of mementos (when to discard old ones).
ℹ️ Where .NET uses it

Undo/redo stacks in editors and design tools are Memento (often paired with Command). Serializing an object's state to persist and reload it is a memento in spirit. ADO.NET's DataSet keeps original/current row versions to support RejectChanges() β€” a memento-style rollback.

πŸ”€ Easily confused

Memento pairs with Command (a command stores a memento to know how to undo itself) and Iterator (a memento can capture iteration position). Don't confuse it with Prototype: Prototype clones a whole object to make new ones; Memento captures just enough state to restore the same object later. More in Chapter 12.

Observer behavioral

When one object changes, automatically notify all its dependents

Observer defines a one-to-many dependency: when a "subject" (publisher) changes state, every registered "observer" (subscriber) is notified and updated automatically. Subscribers opt in and out at will; the publisher never hard-codes who's listening.

🌍 Real-world analogy

A YouTube channel (the slides use a newspaper β€” same idea). You subscribe, and every new video lands in your feed automatically β€” you don't refresh the channel page daily hoping for uploads. The channel keeps a subscriber list and pushes notifications; you can unsubscribe any time and it stops. The channel doesn't know or care who you are.

The problem

A store gets new stock; customers want that specific product. Two bad options: (1) each customer checks the store every day β€” wasteful polling; or (2) the store emails every customer about every product β€” spam, and it couples the store to the whole customer list. Hard-coding notifications is worse still: the store class swells every time a new kind of interested party appears.

❌ Naive: publisher hard-wires every interested party
public class Store
{
    public void AddStock(string product)
    {
        // The store must KNOW about every listener type:
        _emailService.Notify(product);
        _smsService.Notify(product);
        _analytics.Record(product);
        _mobileApp.Push(product);   // add a new channel? edit THIS method.
    }
}
Every new subscriber type forces a change to Store β€” violates Open/Closed and couples the store to unrelated services.

The solution

The insight: give the publisher a subscription list of objects that share one notification interface, and loop over it when something happens. The publisher knows only "IObserver" β€” not the concrete types. Adding a new subscriber means writing a new class and calling Subscribe; the publisher never changes. The notebook models an editor firing open/save events β€” we'll keep that domain.

Events.cs
// The Observer interface: the notification contract.
public interface IEventListener
{
    void Update(string eventType, string file);
}

// The Subject / Publisher: manages subscriptions and notifies.
public class EventManager
{
    private readonly Dictionary<string, List<IEventListener>> _listeners = new();

    public void Subscribe(string eventType, IEventListener l)
    {
        if (!_listeners.ContainsKey(eventType))
            _listeners[eventType] = new();
        _listeners[eventType].Add(l);
    }

    public void Unsubscribe(string eventType, IEventListener l)
        => _listeners.GetValueOrDefault(eventType)?.Remove(l);

    public void Notify(string eventType, string file)
    {
        if (_listeners.TryGetValue(eventType, out var ls))
            foreach (var l in ls) l.Update(eventType, file);
    }
}
Listeners.cs
public class Editor          // owns a publisher
{
    public EventManager Events { get; } = new();
    private string _file = "";

    public void OpenFile(string file)
    {
        _file = file;
        Events.Notify("open", file);   // broadcast
    }
    public void SaveFile() => Events.Notify("save", _file);
}

public class EmailAlertListener : IEventListener
{
    private readonly string _email;
    public EmailAlertListener(string email) => _email = email;
    public void Update(string type, string file)
        => Console.WriteLine($"Email {_email}: '{type}' on {file}");
}

public class LogListener : IEventListener
{
    public void Update(string type, string file)
        => Console.WriteLine($"Log: '{type}' on {file}");
}
Program.cs
var editor = new Editor();
var email = new EmailAlertListener("boss@corp.com");
var log   = new LogListener();

editor.Events.Subscribe("open", log);
editor.Events.Subscribe("save", log);
editor.Events.Subscribe("save", email);   // email only cares about saves

editor.OpenFile("report.txt");  // -> log only
editor.SaveFile();              // -> log AND email

editor.Events.Unsubscribe("save", email); // boss stops caring
editor.SaveFile();             // -> log only again
Deep dive: C# has Observer built into the language β€” event and delegate

You rarely hand-roll Subscribe/Notify in C#. The event keyword is the Observer pattern, baked in. += subscribes, -= unsubscribes, and invoking the event notifies everyone:

public class Store
{
    // The event = the subscription list. EventHandler<T> = observer interface.
    public event EventHandler<string>? StockAdded;

    public void AddStock(string product)
        => StockAdded?.Invoke(this, product);   // notify all subscribers
}

var store = new Store();
store.StockAdded += (sender, product) =>        // subscribe with a lambda
    Console.WriteLine($"Email: {product} is in stock!");
store.StockAdded += (sender, product) =>
    Console.WriteLine($"SMS: {product} available");

store.AddStock("PS5");   // both handlers fire

There's also IObservable<T>/IObserver<T> (the basis of Reactive Extensions / Rx.NET) β€” the same pattern with explicit interfaces and completion/error signals. Use plain event for simple cases; reach for IObservable when you need streams of events with LINQ-style composition.

EventManager Subscribe / Notify Β«interfaceΒ» IEventListener EmailAlertListener LogListener notifies * β–Έ Publisher loops its subscriber list, calling Update() on each
Figure: Observer β€” one subject, a list of subscribers behind a single interface.
Try it: YouTube channel β€” subscribe, publish, notify

Toggle subscribers on/off, then publish a video. Only currently-subscribed observers get notified. The channel never knows who's listening β€” it just loops the list.

What to notice: unsubscribing removes an observer from the list β€” the next publish skips it, with zero change to the channel's code. That's the one-to-many, loosely-coupled broadcast Observer gives you.

When to use it β€” and when not to

βœ… Reach for Observer when…
  • A change to one object requires updating others, and you don't know how many or which ones.
  • An object should notify others without assuming who they are (loose coupling).
  • Two aspects of a system depend on each other and you want to vary them independently (e.g. model ↔ views).
⚠️ Costs & drawbacks
  • Lapsed-listener leak: forget to unsubscribe and the publisher keeps the observer alive.
  • Notification order is unspecified; cascades of updates can be hard to trace.
  • A slow observer stalls the whole broadcast (unless async).
ℹ️ Where .NET uses it

event/delegate is language-level Observer β€” every UI Click, PropertyChanged (INotifyPropertyChanged powers all of WPF/MAUI data binding), and Timer.Elapsed. IObservable<T> / Rx.NET is Observer for event streams. Related: colleagues in a Mediator often communicate via Observer.

πŸ”€ Easily confused: Observer vs Mediator vs Pub/Sub

See the detailed note under Mediator. Short version: the true split is broadcast vs centralised coordination β€” Observer broadcasts one subject's change to its subscribers, while Mediator centralises the back-and-forth between peers. As a clue (not a law), Observer is usually one-to-many and one-directional (subject β†’ subscribers) and Mediator usually many-to-many and bidirectional (a hub coordinating peers). Pub/Sub adds a broker so publisher and subscriber don't know each other at all. Chapter 12 has the side-by-side.

State behavioral

Let an object change its behavior when its internal state changes

State lets an object alter its behavior when its internal state changes β€” it appears to change its class. Each state becomes its own object; the context delegates to the current state object and swaps it to transition. It's the object-oriented form of a finite-state machine.

🌍 Real-world analogy

Your smartphone's buttons behave differently by state. Unlocked: pressing a button runs an app. Locked: any button shows the unlock screen. Battery critical: any button shows the charging screen. Same buttons, completely different behavior β€” because the phone is in a different state.

The problem

A Document can be Draft, Moderation, or Published. Its publish() method behaves differently in each: Draft β†’ moves to Moderation; Moderation β†’ Published (only if admin); Published β†’ does nothing. The naive version encodes this with conditionals in every method:

❌ Naive: giant conditionals repeated in every method
public class Document
{
    public string State = "draft";

    public void Publish(bool isAdmin)
    {
        if (State == "draft")           State = "moderation";
        else if (State == "moderation") { if (isAdmin) State = "published"; }
        else if (State == "published")  { /* nothing */ }
    }

    public void Render()
    {
        if (State == "draft")           { /* ... */ }
        else if (State == "moderation") { /* ...same switch AGAIN... */ }
        else if (State == "published")  { /* ... */ }
    }
    // Every method repeats the same state switch. Add a state = edit them ALL.
}
The transition logic is smeared across every method as duplicated conditionals. Adding an "Archived" state means touching every method and risking breaking the others.

The solution

The insight: make each state a class implementing a shared interface; the context holds a reference to the current state and delegates to it. Transitioning = replacing the state object. State-specific behavior lives in one place per state, and adding a state means adding a class β€” nothing else changes. States themselves decide the legal transitions (and can reject illegal ones), which is exactly the notebook's game example.

State.cs
// The State interface: the state-dependent actions.
public interface IGameState
{
    void OnStart(Game game);
    void OnPause(Game game);
    void OnEnd(Game game);
    string Name { get; }
}

// The Context: delegates all behavior to its current state.
public class Game
{
    public IGameState State { get; private set; }
    public Game() => State = new WelcomeState();

    public void SetState(IGameState s)
    {
        Console.WriteLine($"  β†’ transition to {s.Name}");
        State = s;
    }

    // The context just forwards β€” no conditionals.
    public void Start() => State.OnStart(this);
    public void Pause() => State.OnPause(this);
    public void End()   => State.OnEnd(this);
}
States.cs
public class WelcomeState : IGameState
{
    public string Name => "Welcome";
    public void OnStart(Game g) => g.SetState(new PlayingState());
    public void OnPause(Game g) => Console.WriteLine("Can't pause on welcome screen");
    public void OnEnd(Game g)   => Console.WriteLine("Nothing to end yet");
}

public class PlayingState : IGameState
{
    public string Name => "Playing";
    public void OnStart(Game g) => Console.WriteLine("Already playing");
    public void OnPause(Game g) => g.SetState(new PausedState());
    public void OnEnd(Game g)   => g.SetState(new GameOverState());
}

public class PausedState : IGameState
{
    public string Name => "Paused";
    public void OnStart(Game g) => g.SetState(new PlayingState()); // resume
    public void OnPause(Game g) => Console.WriteLine("Already paused");
    public void OnEnd(Game g)   => g.SetState(new GameOverState());
}

public class GameOverState : IGameState
{
    public string Name => "Game Over";
    public void OnStart(Game g) => g.SetState(new WelcomeState()); // restart
    public void OnPause(Game g) => Console.WriteLine("Game is over");
    public void OnEnd(Game g)   => Console.WriteLine("Game is already over");
}
Program.cs
var game = new Game();          // starts in Welcome
game.Start();                   // Welcome -> Playing
game.Pause();                   // Playing -> Paused
game.Pause();                   // "Already paused" (rejected)
game.Start();                   // Paused -> Playing (resume)
game.End();                     // Playing -> Game Over
game.Start();                   // Game Over -> Welcome (restart)
Try it: media player state machine

The current state glows in the diagram. Press buttons β€” legal actions transition; illegal ones are rejected by the current state (not by an if in the player).

Stopped Playing Paused
What to notice: pressing "Pause" while Stopped is rejected by the Stopped state object. The player context has no conditionals β€” it just forwards to whatever state it currently holds.

When to use it β€” and when not to

βœ… Reach for State when…
  • An object's behavior depends on its state and must change at run-time.
  • Methods are full of large conditionals branching on a state field.
  • You have a genuine finite-state machine with defined transitions.
⚠️ Costs & drawbacks
  • A class per state β€” overkill for two states that rarely change.
  • Transition logic gets distributed across state classes; the "big picture" of the machine can be harder to see in one place.
ℹ️ Where .NET uses it

Workflow engines and Stateless (a popular C# state-machine library) implement State. TcpClient/socket connection lifecycles, and UI wizards that enable/disable actions per step, are State machines. System.Threading.Tasks.Task exposes a Status that behaves like state, too.

πŸ”€ Easily confused: State vs Strategy (the #1 mix-up)

Their UML is identical β€” a context delegating to an interface with interchangeable implementations. The core intent differs: State models behaviour that varies by the context's internal mode (the object appears to "change class" as its state changes); Strategy models interchangeable algorithms chosen from outside. A common clue β€” but not the definition β€” is who switches: Strategy objects are independent and unaware of each other while the client picks one, whereas State objects often know about and switch to each other from inside as the context runs. Treat that as a tendency: some state machines centralise transitions in the context, and some strategies are swapped internally, so lean on intent first. Strategy answers "how should I do this job?"; State answers "what am I right now, and what can I do?" This is the single most important distinction on the page β€” full treatment in Chapter 12.

Strategy behavioral

Encapsulate a family of algorithms and make them swappable at run-time

Strategy defines a family of interchangeable algorithms, wraps each in its own object, and lets the client choose which one the context uses β€” swapping the whole algorithm without touching the code that uses it.

🌍 Real-world analogy

Getting to the airport. You can take a bus, order a cab, or ride a bike. Each is a transportation strategy. You pick one based on budget and time β€” and you can pick a different one tomorrow. The goal ("reach the airport") is fixed; the algorithm to achieve it is swappable.

The problem

A navigation app started with car routing. Then came walking routes, then cycling, then public transport. Each new algorithm was bolted into the one giant Navigator class, which doubled in size each time. Worse, a bug fix in the cycling logic risked breaking the (already-working) car logic, because it all lived in one class riddled with conditionals.

❌ Naive: one class, one method, many conditionals
public class Navigator
{
    public Route Build(Point a, Point b, string mode)
    {
        if (mode == "car")        { /* road algorithm */ }
        else if (mode == "walk")  { /* pedestrian algorithm */ }
        else if (mode == "bike")  { /* cycle-path algorithm */ }
        else if (mode == "bus")   { /* transit algorithm */ }
        // Every new mode grows this method and risks the others.
        return null!;
    }
}
One monster method. Adding a mode edits working code. Violates Open/Closed and Single Responsibility.
βœ… With Strategy
var nav = new Navigator(new CarStrategy());
nav.BuildRoute(a, b);

nav.SetStrategy(new BikeStrategy());  // hot-swap at run-time
nav.BuildRoute(a, b);

// New transport mode = new class implementing IRouteStrategy.
// Navigator never changes.
Each algorithm is isolated. A cycling bug can't touch car routing. Add a mode = add a class.
⚠️ The change request that kills the naive version

"Add ride-share routing, and let users A/B test two cycling algorithms in production." In the monolith you'd edit the shared method (again) and risk everything. With Strategy you drop in RideShareStrategy and BikeStrategyV2 and inject whichever you like β€” no existing algorithm is touched.

The solution

The insight: extract each algorithm into a class behind a common interface; the context holds one and delegates to it, and the client injects the choice. Here's a payment/shipping-cost example (the notebook uses swappable string formatters β€” same shape):

ShippingStrategy.cs
// The Strategy interface: one method, many algorithms.
public interface IShippingStrategy
{
    decimal Cost(decimal weightKg, decimal distanceKm);
}

// The Context: holds a strategy, delegates the calculation.
public class ShippingCalculator
{
    private IShippingStrategy _strategy;
    public ShippingCalculator(IShippingStrategy s) => _strategy = s;

    // Swap the algorithm at run-time:
    public void SetStrategy(IShippingStrategy s) => _strategy = s;

    public decimal Quote(decimal weight, decimal distance)
        => _strategy.Cost(weight, distance);   // no idea which algorithm
}
Strategies.cs
public class StandardShipping : IShippingStrategy
{
    public decimal Cost(decimal w, decimal d) => 5m + w * 0.5m + d * 0.02m;
}

public class ExpressShipping : IShippingStrategy
{
    public decimal Cost(decimal w, decimal d) => 12m + w * 1.2m + d * 0.05m;
}

public class DroneShipping : IShippingStrategy
{
    // Different rules entirely β€” capped distance, flat premium.
    public decimal Cost(decimal w, decimal d)
        => d > 20 ? decimal.MaxValue : 20m + w * 2m;
}
Program.cs
var calc = new ShippingCalculator(new StandardShipping());
Console.WriteLine(calc.Quote(2m, 100m));   // standard

calc.SetStrategy(new ExpressShipping());   // client hot-swaps
Console.WriteLine(calc.Quote(2m, 100m));   // express

calc.SetStrategy(new DroneShipping());
Console.WriteLine(calc.Quote(2m, 5m));     // drone
πŸ’‘ In C#, a strategy can be a lambda

The notebook passes plain functions (lowercase_formatter, uppercase_formatter) as strategies. In C# you can do the same with Func<>: new ShippingCalculator(( w, d) => 5 + w*0.5m). Use a full interface when the strategy needs state or multiple methods; use a delegate for one-liners.

Try it: shipping-cost calculator with swappable strategy

Set weight and distance, then pick a strategy from the dropdown. The same calculator produces different costs because you swapped the algorithm object inside it β€” no branching.

What to notice: the calculator's Quote() code never changes. Choosing "Drone" injects a completely different algorithm β€” including its own rule that it refuses distances over 20 km.

When to use it β€” and when not to

βœ… Reach for Strategy when…
  • Many related classes differ only in behavior β€” configure one class with one of several behaviors.
  • You need different variants of an algorithm (speed vs memory, exact vs approximate).
  • A class has multiple conditional branches selecting behavior β€” lift each branch into a strategy.
  • An algorithm uses data the client shouldn't see β€” hide it inside the strategy.
⚠️ Costs & drawbacks
  • The client must know the strategies exist to choose one.
  • More objects; overkill if behavior never varies.
  • Communication overhead if the context passes lots of data the strategy ignores.
ℹ️ Where .NET uses it

IComparer<T> passed to List.Sort() is a Strategy (the comparison algorithm is swappable). StringComparer.OrdinalIgnoreCase vs InvariantCulture are strategies. ASP.NET Core's dependency injection injects strategy implementations by interface all day long. LINQ's custom key selectors and the HttpClient retry policies (Polly) are strategies too. Related: strategy objects often make good Flyweights.

πŸ”€ Easily confused

Strategy vs State: identical UML, different intent β€” see the big note under State. Strategy vs Command: Strategy is an algorithm you swap; Command is a request you store/queue/undo β€” see the note under Command. Strategy vs Template Method: both vary parts of behavior, but Template Method uses inheritance to vary steps of a fixed skeleton, while Strategy uses composition to swap the whole algorithm β€” see the note under Template Method. All three in Chapter 12.

Template Method behavioral

Fix an algorithm's skeleton in a base class; let subclasses fill specific steps

Template Method defines the skeleton of an algorithm in one method, deferring some steps to subclasses. Subclasses redefine certain steps without changing the algorithm's overall structure. It's a class pattern β€” it uses inheritance, not composition.

🌍 Real-world analogy

Mass-produced houses. The architectural plan fixes the sequence β€” lay foundation, frame, build walls, run plumbing and wiring, finish. Every house follows that skeleton. But each owner can tweak specific steps: brick vs timber walls, this kitchen layout vs that. The overall algorithm (the build order) is invariant; the individual steps have extension points.

The problem

A data-mining app extracts data from PDF, DOC, and CSV files. The file-reading code differs per format, but the surrounding steps β€” open, parse, analyse, generate a report, close β€” are almost identical across all three. Copy-pasting that shared skeleton into three classes means three copies of the analysis logic to keep in sync.

❌ Naive: duplicate the whole algorithm per format
public class PdfMiner
{
    public void Mine(string path)
    {
        OpenPdf(path);
        var raw = ParsePdf();
        var data = Analyse(raw);     // ← identical everywhere
        SendReport(data);            // ← identical everywhere
        ClosePdf();
    }
}
public class CsvMiner
{
    public void Mine(string path)
    {
        OpenCsv(path);
        var raw = ParseCsv();
        var data = Analyse(raw);     // ← COPY-PASTED
        SendReport(data);            // ← COPY-PASTED
        CloseCsv();
    }
}
The invariant steps (Analyse, SendReport) are duplicated. Fix a bug in analysis and you must fix it in every miner. The algorithm's shape is repeated too.

The solution

The insight: put the fixed step sequence in a non-overridable "template method" on an abstract base class; make the varying steps abstract (or virtual hooks) for subclasses to fill. The base class owns the algorithm's structure; subclasses supply only what differs. The notebook uses a data-storage example; we'll use the classic tea-vs-coffee beverage skeleton because it makes the fixed-vs-variable split crystal clear.

Beverage.cs
public abstract class Beverage
{
    // THE TEMPLATE METHOD β€” the fixed skeleton. Not virtual:
    // subclasses must not change the algorithm's structure.
    public void Prepare()
    {
        BoilWater();          // fixed step (shared)
        Brew();               // varies β€” abstract
        PourInCup();          // fixed step (shared)
        if (WantsCondiments())  // hook β€” optional override
            AddCondiments();  // varies β€” abstract
    }

    // Fixed steps: implemented once here.
    private void BoilWater() => Console.WriteLine("Boiling water");
    private void PourInCup() => Console.WriteLine("Pouring into cup");

    // Variable steps: subclasses MUST implement.
    protected abstract void Brew();
    protected abstract void AddCondiments();

    // A HOOK: default behavior a subclass MAY override.
    protected virtual bool WantsCondiments() => true;
}
Tea.cs / Coffee.cs
public class Tea : Beverage
{
    protected override void Brew()
        => Console.WriteLine("Steeping the tea bag");
    protected override void AddCondiments()
        => Console.WriteLine("Adding lemon");
}

public class Coffee : Beverage
{
    protected override void Brew()
        => Console.WriteLine("Dripping coffee through filter");
    protected override void AddCondiments()
        => Console.WriteLine("Adding sugar and milk");

    // Overrides the hook to skip a step:
    protected override bool WantsCondiments() => false;
}
Program.cs
Beverage tea = new Tea();
tea.Prepare();
// Boiling water / Steeping the tea bag / Pouring / Adding lemon

Console.WriteLine("---");

Beverage coffee = new Coffee();
coffee.Prepare();
// Boiling water / Dripping coffee / Pouring
// (condiments skipped β€” the hook returned false)
Try it: run the beverage algorithm (fixed vs hook steps)

Run the same template method for tea vs coffee. Each step lights up as it runs β€” blue = base-class fixed step, purple = subclass-supplied step, amber = optional hook.

What to notice: the order of steps never changes β€” that's the template method, locked in the base class. Only the brew/condiment steps differ, and coffee's hook skips condiments entirely. Subclasses can't reorder the algorithm.

When to use it β€” and when not to

βœ… Reach for Template Method when…
  • Several classes share an algorithm's structure but differ in a few steps β€” factor the invariant parts up, leave the variable parts abstract.
  • You want to control exactly where subclasses may extend (via hooks) and forbid changing the rest.
  • You're fighting duplication across near-identical algorithms.
⚠️ Costs & drawbacks
  • Relies on inheritance β€” a subclass is locked to one base skeleton; you can't swap behavior at run-time.
  • Can violate the Liskov principle if a subclass's step breaks the base's assumptions.
  • The "inverted" control flow (base calls down into subclass) can surprise readers β€” the Hollywood Principle: "don't call us, we'll call you".
ℹ️ Where .NET uses it

ASP.NET Core's ControllerBase and the request pipeline call your overridden action methods at fixed points. Stream's Read/Write skeleton with abstract members, background-service base classes (BackgroundService.ExecuteAsync), and Comparer<T>/EqualityComparer<T> base classes all use Template Method. Related: template methods often call Factory Methods for their varying steps.

πŸ”€ Easily confused: Template Method vs Strategy

Both let you vary part of a behavior. The mechanism differs. Template Method uses inheritance: the varying steps are methods a subclass overrides, chosen at compile time, and only parts of a fixed algorithm vary. Strategy uses composition: the whole algorithm is a separate object you can inject and swap at run-time. Rule of thumb: need to change behavior at run-time, or avoid a deep class hierarchy? β†’ Strategy. Have a fixed sequence with a couple of pluggable steps and are happy with subclasses? β†’ Template Method. Side-by-side in Chapter 12.

Visitor behavioral

Add new operations to a class hierarchy without editing the classes

Visitor lets you define a new operation over a set of object types without changing those types. You move the operation into a separate "visitor" object; each element "accepts" a visitor and calls back the method for its own type. This is the hardest pattern on the page, entirely because of one mechanism: double dispatch. We'll build up to it slowly.

🌍 Real-world analogy

An insurance agent visiting different buildings. At a home he pitches medical insurance; at a bank, theft insurance; at a coffee shop, fire-and-flood. The buildings don't change β€” they just let the agent in ("accept" him). The agent (visitor) carries the type-specific behavior. Next month a different agent (a new operation) can visit the same buildings with a whole new set of pitches, and again the buildings need no changes.

The problem

Your app models geographic data as a graph of node types (City, Road, Industry…), all in production. Task: export the graph to XML. The obvious move is to add an ExportXml() method to every node class. But the architect forbids editing the node classes β€” they're battle-tested and he won't risk a bug. And even if you could, next week they'll want JSON export, then CSV β€” a new method on every class each time. The operations pollute the data classes.

❌ Naive: bolt every new operation onto every node class
public class City : Node
{
    public void ExportXml()  { /* ... */ }
    public void ExportJson() { /* added later β€” edit City again */ }
    public void ExportCsv()  { /* and again... */ }
}
// Same three methods copy-pasted into Road, Industry, ...
// Each new operation touches EVERY node class.
Operations and data are tangled. Adding an operation edits every class (risky, forbidden). The node classes accumulate unrelated responsibilities.

Why a simple switch doesn't work β€” and what "dispatch" means

You might think: keep the operation separate, and inside it just check the type. Like this:

public void Export(Node node)   // one external method
{
    if (node is City c)          { /* export city */ }
    else if (node is Road r)     { /* export road */ }
    else if (node is Industry i) { /* export industry */ }
    // add a node type => edit this switch. And you lose static type safety.
}

That works but it's the very type-switch we're trying to avoid, and the compiler can't help you when you add a node type. The clean solution needs the language to pick the right method for us β€” twice. To see why "twice", you need the idea of dispatch.

🧠 Single vs double dispatch β€” the crux of Visitor

Dispatch = how the runtime decides which method implementation to actually call.

Single dispatch is what normal virtual methods do: the method chosen depends on one type β€” the runtime type of the object you call it on. When you write node.Accept(v), C# picks Accept based on the real type of node (City? Road?). That's one dimension of "which method".

But Visitor needs the call to depend on two types at once: the element type (City vs Road) and the visitor type (XmlExporter vs AreaCalculator). Choosing a method by two runtime types is double dispatch, and C#/Java/C++ don't have it built in. Visitor simulates it with two chained single-dispatch calls.

The solution: Accept(visitor) β†’ visitor.Visit(this), the two-hop bounce

The insight: each element implements Accept(visitor) whose one job is to call visitor.Visit(this). Because this has a known compile-time type inside that class, the second call resolves to the correctly-typed Visit overload. Two single dispatches = the effect of double dispatch. Follow the bounce:

  1. Hop 1 (dispatch on element): you call node.Accept(visitor). Virtual dispatch picks City.Accept because node is really a City.
  2. Hop 2 (dispatch on visitor): inside City.Accept, the code is visitor.Visit(this). Here this is statically a City, so the compiler binds to the Visit(City) overload; virtual dispatch then picks the right visitor (XmlExporter vs AreaCalculator).

Result: the exact method for (this element type Γ— this visitor type) runs β€” with full compile-time checking and no switch. The notebook's contract-reporting example shows it perfectly; here it is in C#.

Contracts.cs
// The Element interface: declares Accept.
public interface IContract
{
    // The parameter is the VISITOR interface. This one line
    // is what makes double dispatch possible.
    T Accept<T>(IReportVisitor<T> visitor);
}

// The Visitor interface: one Visit overload PER concrete element.
public interface IReportVisitor<T>
{
    T Visit(FixedPriceContract c);
    T Visit(TimeAndMaterialsContract c);
    T Visit(SupportContract c);
}
ConcreteContracts.cs
public class FixedPriceContract : IContract
{
    public decimal PricePerYear { get; init; }
    // Accept's ONLY job: bounce to visitor.Visit(this).
    // Inside THIS class, `this` is statically FixedPriceContract,
    // so the correct Visit overload is chosen at compile time.
    public T Accept<T>(IReportVisitor<T> v) => v.Visit(this);
}

public class TimeAndMaterialsContract : IContract
{
    public decimal CostPerHour { get; init; }
    public int Hours { get; init; }
    public T Accept<T>(IReportVisitor<T> v) => v.Visit(this);
}

public class SupportContract : IContract
{
    public decimal CostPerMonth { get; init; }
    public T Accept<T>(IReportVisitor<T> v) => v.Visit(this);
}
Visitors.cs
// Each concrete visitor is a NEW operation over ALL element types β€”
// added without touching a single contract class.
public class MonthlyCostVisitor : IReportVisitor<decimal>
{
    public decimal Visit(FixedPriceContract c) => c.PricePerYear / 12;
    public decimal Visit(TimeAndMaterialsContract c) => c.CostPerHour * c.Hours;
    public decimal Visit(SupportContract c) => c.CostPerMonth;
}

public class YearlyCostVisitor : IReportVisitor<decimal>
{
    public decimal Visit(FixedPriceContract c) => c.PricePerYear;
    public decimal Visit(TimeAndMaterialsContract c) => c.CostPerHour * c.Hours;
    public decimal Visit(SupportContract c) => c.CostPerMonth * 12;
}
Program.cs
var contracts = new List<IContract>
{
    new FixedPriceContract { PricePerYear = 12000 },
    new TimeAndMaterialsContract { CostPerHour = 150, Hours = 10 },
    new SupportContract { CostPerMonth = 500 },
};

var monthly = new MonthlyCostVisitor();
decimal total = 0;
foreach (var c in contracts)
    total += c.Accept(monthly);   // double dispatch, per element
Console.WriteLine($"Monthly total: {total}");

// New report type = new visitor, ZERO changes to contract classes:
var yearly = new YearlyCostVisitor();
Console.WriteLine(contracts.Sum(c => c.Accept(yearly)));
The two-hop bounce (double dispatch) Client City (element) Accept(v) XmlExporter Visit(City) β‘  node.Accept(v) dispatch on element type β‘‘ v.Visit(this) dispatch on visitor type runs Visit(City) on XmlExporter β€” the (element Γ— visitor) method Two single dispatches, chained, = the effect of double dispatch.
Figure: the Accept(visitor) β†’ visitor.Visit(this) bounce is the trick. Hop β‘  (virtual Accept) selects the element method; hop β‘‘ uses the now-known element type to select the Visit overload.
Try it: run different visitors over the same structure

Same list of shapes. Pick a visitor and run it β€” Area calculator vs SVG exporter. Each shape's Accept bounces to the right Visit overload for its type. Adding a visitor needs zero changes to the shape classes.

What to notice: the shapes (Circle, Rectangle, Triangle) never change between runs. Switching the dropdown swaps the operation β€” that's the whole point of Visitor: new operations without touching the element classes. Each line shows which Visit overload fired.

When to use it β€” and when not to

βœ… Reach for Visitor when…
  • You have a stable class hierarchy and keep needing new operations over it.
  • Many unrelated operations would otherwise pollute the element classes.
  • The element structure rarely changes but the operations change often.
⚠️ Costs & drawbacks
  • Adding a new element type is painful: you must add a Visit overload to every visitor. Visitor trades "easy new operations" for "hard new elements" β€” the exact opposite trade-off of normal polymorphism.
  • Elements must expose enough state for visitors to work, which can weaken encapsulation.
  • The double-dispatch indirection is genuinely hard for newcomers to read.
ℹ️ Where .NET uses it

The Roslyn compiler uses CSharpSyntaxVisitor/SyntaxWalker to traverse and transform syntax trees β€” new analyses = new visitors, no change to the node types. Expression trees (ExpressionVisitor) let LINQ providers translate queries. Related: Visitor is often applied over a Composite structure, and can implement the operations of an Interpreter's AST.

πŸ”€ Easily confused

Visitor vs Iterator: Iterator traverses; Visitor operates on each element by type β€” they're complementary. Visitor vs Strategy: both externalise behavior, but Strategy swaps one algorithm behind one method, while Visitor dispatches to a family of type-specific methods via Accept. More in Chapter 12.

Interpreter behavioral

Represent a simple language's grammar as classes, and evaluate sentences by recursion

Interpreter says: given a language whose sentences you evaluate often, define a class per grammar rule, build sentences as a tree of those objects (an abstract syntax tree), and give each a method that evaluates itself recursively. The slides treat this one lightly, so we'll keep it focused: structure, a small example, and when it's worth it.

🌍 Real-world analogy

A pocket calculator reading "3 + 5 Γ— 2". It doesn't understand the whole string at once β€” it breaks it into pieces (numbers, operators), arranges them by precedence into a little tree, then evaluates from the leaves up. Each operator "knows" how to combine its two sub-results. That grammar-as-objects, evaluate-recursively idea is Interpreter.

Structure & participants

The tree itself is a Composite: terminals are leaves, non-terminals are composites holding children. The notebook builds a small arithmetic interpreter β€” here it is in C#.

Expressions.cs
// AbstractExpression
public interface IExpression { double Interpret(); }

// Terminal expression: a literal number.
public class Number : IExpression
{
    private readonly double _value;
    public Number(double value) => _value = value;
    public double Interpret() => _value;   // interprets to itself
}

// Non-terminal expressions: combine two sub-expressions.
public class Add : IExpression
{
    private readonly IExpression _l, _r;
    public Add(IExpression l, IExpression r) { _l = l; _r = r; }
    public double Interpret() => _l.Interpret() + _r.Interpret();  // recursion
}

public class Multiply : IExpression
{
    private readonly IExpression _l, _r;
    public Multiply(IExpression l, IExpression r) { _l = l; _r = r; }
    public double Interpret() => _l.Interpret() * _r.Interpret();
}
Program.cs
// Build the AST for "3 + 5 * 2" (5*2 grouped by precedence):
//        Add
//       /   \
//   Number(3) Multiply
//             /      \
//        Number(5)  Number(2)
IExpression tree =
    new Add(
        new Number(3),
        new Multiply(new Number(5), new Number(2)));

Console.WriteLine(tree.Interpret());   // 13

// Each node evaluates its children recursively, leaves first.
// A real client parses text into this tree; the grammar
// lives in the class structure itself.
⚠️ When NOT to use Interpreter

It only pays off for simple, stable grammars. As the grammar grows, the class hierarchy explodes and becomes unmanageable β€” the slides say it outright. For anything non-trivial, use a parser generator (ANTLR) or a real parsing library, which are faster and don't force a class per rule. Also skip it when raw efficiency matters: production interpreters compile to bytecode rather than walking a parse tree.

When to use it

βœ… Consider Interpreter when…
  • A recurring problem is naturally expressed as sentences in a small, simple language (search filters, validation rules, simple formulas).
  • The grammar is small and won't grow much.
  • Efficiency isn't critical.
ℹ️ Where .NET uses it
  • System.Text.RegularExpressions interprets a regex grammar.
  • LINQ providers interpret expression trees (often via a Visitor).
  • DataColumn.Expression and rules engines interpret little DSLs.
πŸ”€ Easily confused

The AST is a Composite; a Visitor can hold the per-node operations so you can add new operations (evaluate, pretty-print, optimise) without editing node classes; an Iterator can traverse the tree; and Flyweight can share terminal symbols. Interpreter is really a meeting point of several patterns.

All eleven at a glance

One-line intent and the trigger that tells you to reach for each. Bookmark this table; Chapter 12 expands the confusable pairs.

PatternIntent (one line)Use when…
Chain of ResponsibilityPass a request along a line of handlers until one handles it.Several objects might handle a request and the handler isn't known in advance (pipelines, middleware).
CommandWrap a request as an object you can store, queue, log, and undo.You need undo/redo, queuing, or to parameterise objects with actions.
IteratorTraverse a collection without exposing its internal structure.You want uniform, multiple, or polymorphic traversal over a collection.
MediatorCentralise many-to-many communication in one hub object.Objects are entangled in a web of direct references (chat, UI dialogs, ATC).
MementoSnapshot & restore an object's state without breaking encapsulation.You need undo/checkpoints but must keep state private.
ObserverNotify many dependents automatically when one object changes.One-to-many, loosely-coupled event broadcast (events, data binding).
StateChange an object's behavior by swapping its current state object.You have a finite-state machine and methods full of state conditionals.
StrategyEncapsulate interchangeable algorithms and swap them at run-time.You need selectable/variant algorithms without conditionals in the client.
Template MethodFix an algorithm's skeleton; let subclasses fill specific steps.Several algorithms share a structure but differ in a few steps (inheritance is fine).
VisitorAdd new operations to a class hierarchy without editing it (double dispatch).A stable set of element types needs a growing set of operations.
InterpreterModel a simple grammar as classes and evaluate sentences recursively.A small, stable language recurs and efficiency isn't critical.
πŸ’‘ A memory hook for the whole family

Who's varying, and how? Strategy/State vary an object you delegate to (swap it out). Template Method varies steps via subclassing. Command/Memento vary through time (queue, undo). CoR/Mediator/Observer vary communication (chain, hub, broadcast). Iterator/Visitor vary traversal & operations over a structure. Interpreter varies a language. Name the axis of variation and the pattern usually names itself.

🧠 The one thing to remember

Behavioral patterns all answer the same question β€” "how should responsibility and communication be divided so the design stays flexible as requirements change?" β€” from different angles. Don't memorise UML; memorise the problem each one solves and the change request that would break the naive alternative. That's what makes you reach for the right one in real code, and it's how Chapter 6 combines several at once.