CHAPTER 12 Β· MASTERY

Confusable Patterns, Disambiguated

Once you have met all 23 patterns, the hard part is no longer what each one does β€” it is telling apart the ones that look almost identical. Strategy and State share the same UML. Adapter, Decorator, Proxy, Facade and Bridge are all "an object that wraps another object." This is the page you reach for when two patterns blur together: each cluster below shows why they look the same, a side-by-side table, the smallest possible C# that makes the difference visible, and a litmus-test question you can ask yourself to decide in seconds.

πŸ’‘ How to read this page

Every distinction here comes down to intent, not structure. Two patterns can have byte-for-byte identical class diagrams and still be different patterns, because a pattern is defined by the problem it solves and who is in control β€” not by the shape of the boxes. When you are stuck, don't ask "what does the code look like?"; ask "who drives the change, and why?" Start with the interactive disambiguator, then dive into whichever cluster is biting you.

The litmus tests below are heuristics β€” fast clues that are usually right. The reliable differentiator is always the pattern's INTENT: what problem it exists to solve and what kind of change it isolates. When a clue and the intent disagree, trust the intent. Read each "clue in code" row as a tendency with counterexamples, not a law.

The pattern disambiguator

Answer one question at a time. Each choice narrows the field and logs the reasoning, so by the end you not only get a pattern name β€” you get the chain of "because…" that led there. That reasoning is the transferable skill; the answer is just the by-product.

Try it: walk the litmus tree
What to notice: the same first question ("what kind of problem?") sends you down completely different branches. Patterns are organised by intent family first, then by the fine distinction inside the family. If your real problem doesn't fit a branch cleanly, that is often a sign you are combining two patterns β€” which is normal and healthy.

1 Β· Strategy vs State

This is the classic trap, and for a good reason: their UML is identical. Both have a context object that holds a reference to an interface, and several concrete classes implementing that interface. If you photographed the two class diagrams and swapped the labels, nobody could tell.

πŸ”€ Why they look the same

Context β†’ holds an IThing β†’ several ConcreteThings implement it. Same picture. The difference is who replaces the object, and whether the objects know about each other.

Strategy Context strategy.Execute() IStrategy (A / B / C) client sets it State Context state.Handle(this) IState (A / B / C) state sets next state Same boxes. The arrows of control differ.
Figure: The structures are twins. In Strategy the client swaps the object; in State the objects swap themselves.
QuestionStrategyState
IntentPick one of several interchangeable algorithms.Let an object change its behaviour when its internal state changes β€” as if it changed class.
Who drives the change?The client / outside code sets the strategy.The states themselves (or the context) trigger the transition to the next state.
Do the concrete objects know each other?No. A strategy is oblivious to other strategies.Yes (usually). A state knows which state comes next.
LifetimeTypically set once and left alone.Changes repeatedly over the object's life.
Clue in codecontext.SetStrategy(new Quicksort()) called from outside.context.State = new RedLight() called from inside a state's Handle.
Strategy β€” client chooses, states are inert
// Client decides which algorithm; strategies never swap themselves.
var ctx = new Sorter();
ctx.SetStrategy(new QuickSort());   // outside code picks
ctx.Sort(data);
ctx.SetStrategy(new MergeSort());   // outside code picks again
ctx.Sort(data);

class QuickSort : ISortStrategy {
    public void Sort(int[] a) { /* ... */ }   // knows nothing of MergeSort
}
The QuickSort object has no opinion about what runs next. It cannot reach the context to change anything.
State β€” the state swaps itself
// A state transitions the context to the NEXT state from inside Handle.
class RedLight : ITrafficState {
    public void Next(TrafficLight ctx) {
        Console.WriteLine("Red β†’ Green");
        ctx.State = new GreenLight();   // the state drives the change
    }
}
// Client just pushes time forward; it never names GreenLight.
light.Next();  // Red decides Green is next
light.Next();  // Green decides Yellow is next
RedLight holds a reference back to the context and reassigns ctx.State. The client never chooses the next behaviour.
πŸ’‘ Litmus test

"When the behaviour changes, who made that happen?" If your code reached in and set it β†’ Strategy. If the object flipped itself because something happened to it β†’ State. Follow-up: "Do the alternatives know about each other?" Strategies are strangers; states are a linked graph of transitions.

πŸŽ“ Lecture view vs reality

For the exam: in Strategy the client swaps the object; in State the objects swap themselves. In practice: "who swaps the object" is a tendency, not a definition β€” a context can reselect its own Strategy from runtime conditions, and a State machine can be driven by an outside controller. What actually separates them is intent: Strategy makes one interchangeable algorithm pluggable; State models mode-dependent behaviour where the modes form a transition graph. When the clue and the intent disagree, trust the intent.

Full treatment with problem-first walkthroughs: Strategy and State in Chapter 5.

2 Β· The wrapper family: Adapter Β· Decorator Β· Proxy Β· Facade Β· Bridge

All five are "an object that stands in front of another object and forwards calls to it." That single sentence is why students mix them up. The way to separate them is one question asked in a specific order: what happens to the interface?

🧠 The interface litmus β€” memorise this

Adapter β€” changes the interface (incompatible β†’ expected).
Decorator β€” keeps the same interface, but adds behaviour (and stacks).
Proxy β€” keeps the same interface, but controls access to the real object.
Facade β€” introduces a new, simpler interface over many objects.
Bridge β€” is designed up front to split one hierarchy into two that vary independently.

AdapterDecoratorProxyFacadeBridge
IntentMake an incompatible interface usable.Add responsibilities dynamically.Stand in for / guard the real object.Simplify a complex subsystem.Split abstraction from implementation so both vary.
Interface vs wrappedDifferent β€” that's the whole point.Same β€” enriched.Same β€” identical signature.New & smaller than the parts.Same abstraction, swappable impl.
How many objects?One (or two) legacy objects.One, but stackable (chain).Exactly one real subject.Many subsystem objects.Two parallel hierarchies.
Who drives changeExisting incompatible code.Client stacks features at runtime.Access/lifecycle concerns (lazy, remote, security, cache).Client wants one easy door.Designer, before writing subclasses.
Clue in codeMethod names don't match; you translate them.Same method calls inner + does extra.Same method, but adds a check / lazy-init / network hop.One class calling 5 others in sequence.Two fields: an abstraction holding an impl interface.

The three "same interface" wrappers are the sharpest trap. Here they are as tiny contrasts β€” notice the method signature is the same in all three; only the body reveals which pattern it is.

Interface changes. The client wants IPrinter.Print(text) but the library only offers LegacyWriter.WriteLine(...). The adapter translates.

interface IPrinter { void Print(string text); }

class LegacyWriter {                       // incompatible: wrong method name/shape
    public void WriteLine(string s) => Console.WriteLine(s);
}

class PrinterAdapter : IPrinter {          // NEW interface over OLD object
    private readonly LegacyWriter _legacy = new();
    public void Print(string text) => _legacy.WriteLine(text);  // translate call
}
πŸ’‘ Litmus

Do the method names in and out differ? You are translating an interface β†’ Adapter.

Same interface, extra behaviour, stackable. A decorator Is-A the thing it wraps, so you can wrap the wrapper.

interface ICoffee { decimal Cost(); }

class Espresso : ICoffee { public decimal Cost() => 2.0m; }

class MilkDecorator : ICoffee {            // SAME interface as what it wraps
    private readonly ICoffee _inner;
    public MilkDecorator(ICoffee inner) => _inner = inner;
    public decimal Cost() => _inner.Cost() + 0.5m;   // delegate + add
}
// Stackable β€” the tell-tale sign:
ICoffee c = new MilkDecorator(new MilkDecorator(new Espresso())); // 3.0m
πŸ’‘ Litmus

Same interface, and you can wrap it twice to add up features? β†’ Decorator.

Same interface, but it controls access β€” lazy loading, a permission check, caching, or a remote call. It stands in for exactly one real subject.

interface IImage { void Display(); }

class RealImage : IImage {                 // expensive: loads from disk in ctor
    public RealImage(string f) { Console.WriteLine($"loading {f}"); }
    public void Display() => Console.WriteLine("drawing");
}

class ImageProxy : IImage {                // SAME interface, guards creation
    private readonly string _file; private RealImage? _real;
    public ImageProxy(string f) => _file = f;
    public void Display() {
        _real ??= new RealImage(_file);    // lazy: only load on first use
        _real.Display();
    }
}
πŸ’‘ Litmus

Same interface, but the body gatekeeps the real object (when/if/who)? β†’ Proxy.

A new, smaller interface over many collaborators. The facade knows the messy sequence so the client doesn't have to.

// Subsystem: three fiddly classes with their own APIs.
class Amplifier { public void On() {} public void SetVolume(int v) {} }
class Projector { public void On() {} public void WideScreen() {} }
class Lights    { public void Dim(int pct) {} }

class HomeTheaterFacade {                   // ONE new door over MANY objects
    private readonly Amplifier _a = new(); private readonly Projector _p = new();
    private readonly Lights _l = new();
    public void WatchMovie() {              // the simple new interface
        _l.Dim(10); _p.On(); _p.WideScreen(); _a.On(); _a.SetVolume(8);
    }
}
πŸ’‘ Litmus

One method orchestrating several unrelated objects into a simpler API? β†’ Facade.

Designed up front to split two dimensions. Not a retrofit like Adapter β€” you plan the split so shapes and rendering back-ends multiply independently, avoiding a class explosion.

interface IRenderer { void DrawCircle(float r); }     // implementation side
class VectorRenderer : IRenderer { public void DrawCircle(float r){} }
class RasterRenderer : IRenderer { public void DrawCircle(float r){} }

abstract class Shape {                                  // abstraction side
    protected readonly IRenderer R;                     // the BRIDGE field
    protected Shape(IRenderer r) => R = r;
    public abstract void Draw();
}
class Circle : Shape {
    private readonly float _r;
    public Circle(IRenderer r, float radius) : base(r) => _r = radius;
    public override void Draw() => R.DrawCircle(_r);
}
// Any Shape Γ— any Renderer, no Circle-Vector / Circle-Raster subclass blowup.
πŸ’‘ Litmus

Two things vary at the same time and you split them before writing subclasses? β†’ Bridge.

πŸ”€ Adapter vs Bridge β€” the subtle pair

Both hold a reference to another object and forward to it. The difference is timing and intent: Adapter is a rescue β€” you have two things that already exist and don't fit, so you retrofit a translator. Bridge is a plan β€” you foresee two dimensions of change and design the split up front so neither hierarchy grows the other. Adapter changes an interface; Bridge keeps the same abstraction and swaps implementations.

πŸŽ“ Lecture view vs reality

For the exam: the interface litmus holds β€” Adapter changes the interface, Decorator/Proxy keep it, Facade simplifies, Bridge is planned. In practice: the timing clues are clues, not laws. "Bridge is planned, Adapter is a retrofit" is only a tendency β€” Adapter is often designed up front too (the ports & adapters / hexagonal architecture plans its adapters deliberately). And "a Decorator always calls its wrappee" has exceptions: a caching or short-circuiting Decorator can return early without calling the inner object on a hit, while still sharing the interface and stacking. The interface-change litmus is the reliable one; the who-designed-it-when clue is not.

πŸ’‘ The whole family in one decision

Ask, in order: (1) Does the interface change? Yes β†’ Adapter. (2) Is it a simpler interface over many objects? Yes β†’ Facade. (3) Same interface β€” does it control access (lazy/secure/remote/cache)? Yes β†’ Proxy. (4) Same interface β€” does it add stackable behaviour? Yes β†’ Decorator. (5) Was the split designed in advance to let two dimensions vary? β†’ Bridge.

Deep dives: Adapter Β· Decorator Β· Proxy Β· Facade Β· Bridge.

3 Β· Simple Factory Β· Factory Method Β· Abstract Factory Β· Builder

These all "make objects for you so the client doesn't say new ConcreteThing()." They differ in the level of the mechanism β€” is the creation logic a method, a class hierarchy, or a whole object? β€” and in what they create: one product, a family of related products, or one product assembled in steps.

Simple FactoryFactory MethodAbstract FactoryBuilder
What it isAn idiom (not a GoF pattern): one method with a switch.A method subclasses override to choose the product.An object whose methods create a family of matching products.An object that assembles one complex product step by step.
Level of mechanismA static method.Inheritance β€” a virtual method + subclasses.Composition β€” an interface with several create-methods.Composition β€” a builder object with many setters + Build().
ProducesOne product, chosen by a parameter.One product, chosen by which subclass you're in.Several related products that must match.One product with many optional parts.
Varies byA runtime argument.Subclass identity.Which concrete factory you injected.The sequence of steps you call.
Clue in codeCreate(string type) with a switch.protected abstract IProduct CreateProduct();Factory has CreateButton() + CreateCheckbox()..WithX().WithY().Build() chain.

A convenience: hide the switch behind one call. Handy, but adding a type still edits this method (an OCP smell).

static class ShapeFactory {
    public static IShape Create(string kind) => kind switch {
        "circle" => new Circle(),
        "square" => new Square(),
        _ => throw new ArgumentException(kind)
    };
}

The decision moves into a subclass. The base algorithm is fixed; the product it uses is a hole subclasses fill by overriding one method.

abstract class Dialog {
    public void Render() { var b = CreateButton(); b.Paint(); }  // fixed flow
    protected abstract IButton CreateButton();                    // the hook
}
class WindowsDialog : Dialog {
    protected override IButton CreateButton() => new WindowsButton();
}

One factory object makes a whole matching set. Swap the factory and every product changes together, guaranteed consistent.

interface IGuiFactory {                    // a family of create-methods
    IButton CreateButton();
    ICheckbox CreateCheckbox();
}
class MacFactory : IGuiFactory {
    public IButton CreateButton() => new MacButton();
    public ICheckbox CreateCheckbox() => new MacCheckbox();   // always Mac-consistent
}

Not about which class β€” about how much to assemble. Same product type, many optional parts, built by a readable sequence of steps.

var burger = new BurgerBuilder()
    .AddPatty()
    .AddCheese()
    .NoOnions()
    .Build();          // one product, assembled step by step
πŸ”€ Factory Method vs Abstract Factory

Factory Method is one method (via inheritance) making one product. Abstract Factory is one object (via composition) making a family. An Abstract Factory is often implemented with several Factory Methods β€” so it's not "either/or," it's "one is a bigger structure built from the other."

πŸ’‘ Litmus

"How many kinds of thing come out, and how is the choice made?" One product chosen by an argument β†’ Simple Factory. One product chosen by subclass β†’ Factory Method. A matched family chosen by which factory you hold β†’ Abstract Factory. One product with many parts chosen by steps β†’ Builder.

See Factory Method, Abstract Factory and Builder in Chapter 3.

4 Β· Observer Β· Mediator Β· Pub/Sub

All three are about "when something happens, other things react." The real distinctions are who owns the subscription list, whether the publisher knows its subscribers, and whether a broker decouples the two ends in space and time. Whether delivery is synchronous or asynchronous, in-process or distributed, is a separate design choice β€” not part of the definition.

Observer Subject Obs A Obs B C subject knows its list Mediator Hub 1 2 3 4 all talk through the hub Pub/Sub (broker) Pub Broker Sub Sub neither side knows the other
Figure: Observer = one subject with a known list. Mediator = a hub that coordinates peers. Pub/Sub = a broker so publishers and subscribers never meet.
QuestionObserverMediatorPub/Sub (architectural)
ShapeOne subject β†’ many observers.Many peers ↔ one hub.Many publishers β†’ broker β†’ many subscribers.
Do the ends know each other?Subject holds a list of observers (it knows them, by interface).Peers know only the mediator, not each other.Publishers and subscribers know neither β€” only the broker & a topic.
PurposeBroadcast a state change.Untangle many-to-many chatter into a star.Fully decouple senders from receivers across space/time.
Sync / asyncUsually synchronous, in-process.Usually synchronous, in-process.Either β€” a choice, not a rule. In-process & synchronous (C# events, MediatR notifications) or distributed & async (a message bus).
LevelGoF design pattern.GoF design pattern.Architectural pattern (message bus / event grid).
Clue in codesubject.Subscribe(obs); subject calls each observer.Peers call mediator.Notify(this, evt).bus.Publish("topic", msg); no receiver reference anywhere.
Observer β€” subject knows its observers
class Stock {                          // the subject
    private readonly List<IObserver> _obs = new();
    public void Subscribe(IObserver o) => _obs.Add(o);
    public void SetPrice(decimal p) {
        foreach (var o in _obs) o.Update(p);  // subject drives, directly
    }
}
Coupling is subject β†’ observers. The subject holds the list and loops it. One-to-many, synchronous.
Pub/Sub β€” a broker in the middle
bus.Subscribe("prices", msg => Console.WriteLine(msg));  // subscriber side
// ...elsewhere, a totally separate module:
bus.Publish("prices", "AAPL 190");                      // publisher side

// The publisher holds NO reference to any subscriber.
// The broker (bus) matches topic β†’ handlers, often async.
Publisher and subscriber never name each other. Add/remove either side without touching the other. This is the architectural cousin of Observer.
πŸ”€ Observer vs Mediator

Observer is one-directional broadcast β€” the subject shouts, observers listen. Mediator is coordination β€” colleagues send events to the hub, and the hub decides who else needs poking, often calling back multiple colleagues with different messages. If your "subject" starts containing if-this-then-tell-that logic about several unrelated objects, you've grown a Mediator.

πŸ’‘ Litmus

"Is there a thing in the middle, and does either end know the other?" No middle, subject knows its listeners β†’ Observer. A hub that peers route through β†’ Mediator. A broker where neither side knows the other β†’ Pub/Sub. (Async vs sync is a follow-up detail, not the deciding question.)

πŸŽ“ Lecture view vs reality

For the exam: Pub/Sub is the "distributed, asynchronous" cousin of Observer. In practice: that pairing is common but not definitional β€” plenty of pub/sub is in-process and synchronous (raw C# events, or MediatR INotification handlers all run in the same process, on the same thread, by default). What makes it Pub/Sub is a choice: the broker owns the subscription list, the publisher never names its subscribers, and the two are decoupled in space (and optionally time). Durability, offline delivery, and async are broker features you may or may not switch on β€” not things Pub/Sub guarantees.

See Observer and Mediator (Chapter 5), and Publish/Subscribe (Chapter 8).

5 Β· Command vs Strategy

Both turn "a thing you can do" into an object. The difference: a Strategy is a how (an interchangeable algorithm you plug in), while a Command is a what (a request captured as an object β€” with a receiver it acts on, and usually the ability to be queued, logged, or undone).

QuestionStrategyCommand
Answers the question"How should I do this step?""What action should happen (and when)?"
GrammarAn adverb / algorithm (sort quickly).A verb with an object (turn the light on).
Holds a receiver?No β€” it just computes and returns.Yes β€” it knows the object to act on.
Undo / queue / log?No, out of scope.Yes β€” the whole reason it exists, often.
Clue in coderesult = strategy.Execute(input) returns a value.command.Execute() causes a side-effect; often paired with Undo().
Strategy β€” a pluggable algorithm, returns a result
interface IShipping { decimal Cost(Order o); }
class Express : IShipping { public decimal Cost(Order o) => 15m; }

// Context just needs SOME way to compute; it's an algorithm slot.
decimal total = shipping.Cost(order);
No receiver, no undo. It transforms input to output. Swap it to change how cost is computed.
Command β€” a request object with receiver + undo
interface ICommand { void Execute(); void Undo(); }

class TurnOn : ICommand {
    private readonly Light _light;          // the RECEIVER it acts on
    public TurnOn(Light l) => _light = l;
    public void Execute() => _light.On();
    public void Undo()    => _light.Off();  // reversible
}
// Queue them, log them, undo them:
history.Push(cmd); cmd.Execute();  /* later */ history.Pop().Undo();
It bundles receiver + action so the request can be stored, replayed, and reversed. That's what Strategy never does.
πŸ’‘ Litmus

"Does it hold the object it acts on, and could you undo or queue it?" Yes β†’ Command. "Is it a stateless recipe that just returns an answer?" β†’ Strategy.

Command Β· Strategy.

6 Β· Template Method vs Strategy

Both let a fixed process have varying parts. The mechanism is the whole difference: Template Method uses inheritance (subclasses override protected steps of a fixed algorithm baked into a base class), while Strategy uses composition (the context holds a separate object it delegates the varying part to).

Template Method β€” inheritance AbstractReport Generate() {…Step()…} PdfReport : override Step() extends Strategy β€” composition ReportContext Generate(){ fmt.Step() } IFormat ← PdfFormat β—† holds-a
Figure: Template Method varies steps by being a subclass; Strategy varies steps by having a plugged-in object.
QuestionTemplate MethodStrategy
MechanismInheritance (override protected steps).Composition (delegate to a held object).
When is variation chosen?Compile time β€” pick the subclass.Runtime β€” swap the strategy object.
How much varies?Specific steps; the skeleton is fixed & owned by the base.The whole plugged-in algorithm.
Hollywood principleYes β€” base calls down to your overrides ("don't call us, we'll call you").Context calls the strategy, but you can replace it freely.
Clue in codeprotected abstract void Step(); called by a non-virtual template method.A field IStrategy _s; plus a setter.
πŸ’‘ Litmus

"To change the step, do I write a new subclass, or plug in a different object?" Subclass β†’ Template Method. Plug-in β†’ Strategy. Rule of thumb: prefer Strategy (composition over inheritance, Chapter 2) unless the fixed skeleton really is a family invariant you want the base class to own and enforce.

Template Method Β· Strategy.

7 Β· Composite vs Decorator

Both are built by recursive composition β€” an object that implements an interface and also holds reference(s) to objects of that same interface. That shared skeleton is why they're confused. The difference is how many children and why: Composite is a tree of many to represent a part-whole hierarchy; Decorator is a chain of one to layer on behaviour.

Composite β€” tree of many Folder File Folder File 0..* children, uniform treatment Decorator β€” chain of one Milk Sugar Coffee exactly 1 child each, wraps and adds
Figure: Composite branches (many children per node); Decorator is linear (one wrapped child per layer).
QuestionCompositeDecorator
IntentTreat individual objects and groups uniformly (part-whole tree).Add responsibilities to one object, layer by layer.
How many children?Zero to many per node (a collection).Exactly one wrapped component per decorator.
ShapeTree.Linked list / chain.
What it addsStructure β€” grouping.Behaviour β€” extra work around the same call.
Clue in codeList<IComponent> _children; iterated in operations.Single IComponent _inner; field, delegated to then augmented.
πŸ’‘ Litmus

"Does a node hold a list of children, or exactly one wrapped thing?" A list forming a tree β†’ Composite. One inner component being enriched β†’ Decorator. (Fun fact: they compose beautifully β€” a Decorator can wrap a Composite.)

Composite Β· Decorator.

8 Β· Iterator vs Visitor

These pair up so often that people fuse them, but they answer opposite questions. Iterator owns traversal β€” how to walk a collection element by element without exposing its internals. Visitor owns the operation β€” a new algorithm applied to each element without editing the element classes. You often use an Iterator to walk, and a Visitor to do something at each stop.

QuestionIteratorVisitor
OwnsThe traversal (position, order, "next").The operation performed on each element.
Lets you add without editing……new ways to walk a structure.…new operations over a fixed set of element types.
WeaknessDoesn't help add operations.Adding a new element type forces editing every visitor (double-dispatch cost).
MechanismMoveNext() / Current.Double dispatch: element.Accept(visitor) β†’ visitor.Visit(this).
Clue in codeImplements IEnumerator<T> / foreach.Accept(IVisitor v) on elements; Visit(Circle), Visit(Square) on visitors.
🧠 Why they pair

A tree structure exposes an Iterator so callers can walk it without knowing it's a tree. A new report, export, or validation over that tree is a Visitor so you don't reopen every node class each time you invent an operation. Traversal is stable; operations keep multiplying β€” so you separate the two axes of change.

πŸ’‘ Litmus

"Am I adding a way to walk the structure, or a new thing to do at each node?" Walk β†’ Iterator. Do β†’ Visitor. If you find yourself writing Visit(TypeA), Visit(TypeB) overloads, that's Visitor's double dispatch signature.

Iterator Β· Visitor. In .NET, IEnumerator<T> behind foreach is the Iterator pattern.

9 Β· Singleton vs static class vs DI-singleton lifetime

All three give you "just one of these in the whole app." But they differ enormously in testability, polymorphism, and who controls the lifetime β€” and this is where a lot of C# code goes wrong. Singleton is the most-abused pattern; usually what you actually want is the third option.

QuestionSingleton (GoF)static classDI singleton lifetime
What it isA class that enforces one instance via a private ctor + static accessor.A class that can't be instantiated at all; only static members.A normal class registered AddSingleton<T>(); the container makes exactly one.
Implements an interface?Can, but callers usually hit Instance directly (defeats it).No β€” statics can't implement interfaces or be mocked.Yes β€” you depend on IService, not the concrete class.
Testable / mockable?Poorly β€” global access point is a hidden dependency.No β€” hard to fake.Yes β€” inject a fake in tests; that's the point.
Who owns the lifetime?The class itself (global).The runtime (static ctor).The DI container β€” you can change scope in one line.
Holds state?Often (that's the risk β€” global mutable state).Best only for pure functions.Yes, but injected & swappable.
❌ Classic Singleton β€” global access point
class Config {
    private static readonly Config _i = new();
    private Config() { }
    public static Config Instance => _i;   // callers reach in globally
    public string Env { get; set; } = "prod";
}
// Every caller hard-codes Config.Instance β€” a hidden dependency,
// awful to mock, easy to turn into global mutable state.
The static accessor is the smell: it couples every caller to the concrete class and hides the dependency from constructors.
βœ… Prefer DI-singleton lifetime
interface IConfig { string Env { get; } }
class Config : IConfig { public string Env => "prod"; }

// Register once; the container guarantees a single instance.
services.AddSingleton<IConfig, Config>();

// Consumers just ask for the interface β€” mockable, explicit:
class OrderService(IConfig config) { /* uses config.Env */ }
Same "one instance" guarantee, but the dependency is explicit in the constructor and trivially replaced with a fake in tests.
Deep dive: thread-safe C# Singleton if you truly need one

The idiomatic thread-safe form uses Lazy<T>, which handles the locking for you:

class Logger {
    private static readonly Lazy<Logger> _lazy = new(() => new Logger());
    public static Logger Instance => _lazy.Value;   // thread-safe, lazy
    private Logger() { }
}

But before reaching for this, ask whether a DI singleton would do β€” it almost always would, and it keeps your code testable.

πŸ’‘ Litmus

"Do I need one instance, or one global access point?" You almost always need the former, which DI gives you cleanly. Reach for a hand-rolled Singleton only when there's no container and the object genuinely must self-enforce uniqueness. Use a static class only for stateless helpers (pure functions like Math).

Full Singleton treatment (and why it's over-used): Chapter 3.

10 Β· Chain of Responsibility vs Decorator

Both build a linked line of wrapper objects, each holding the next. Structurally, twins again. The behavioural difference is the whole thing: a Decorator always delegates to its inner object and adds something β€” every link runs. A Chain link may stop β€” it decides whether to handle the request itself and halt, or pass it along.

QuestionChain of ResponsibilityDecorator
IntentGive several handlers a shot; one (or none) handles it.Add behaviour; the result accumulates through all layers.
Does every link run?No β€” a link may handle & stop the chain.Yes β€” each layer always delegates inward and adds on.
Goal of passing alongFind the right handler.Compose the full behaviour.
ReturnOften "handled / not handled."The enriched result of the inner call.
Clue in codeif (CanHandle) {…; return;} else _next?.Handle(req);var r = _inner.Do(); return r + extra; β€” always calls inner.
Chain β€” may handle and stop
abstract class Approver {
    protected Approver? Next;
    public void SetNext(Approver n) => Next = n;
    public void Handle(Request r) {
        if (r.Amount <= Limit) Approve(r);   // handle...
        else Next?.Handle(r);                // ...or pass along. Not both.
    }
    protected abstract void Approve(Request r);
}
Exactly one branch runs per link. Once someone approves, the request stops travelling.
Decorator β€” always delegates and adds
class CompressStream : IStream {
    private readonly IStream _inner;
    public CompressStream(IStream inner) => _inner = inner;
    public void Write(byte[] data) {
        var packed = Compress(data);
        _inner.Write(packed);                // ALWAYS calls inner, plus extra
    }
}
There is no "stop here." Every layer runs and contributes; the call always reaches the core object.
πŸ’‘ Litmus

"Can a link decide to not pass the request on?" Yes, one handler ends it β†’ Chain of Responsibility. No, every wrapper always calls inward and adds something β†’ Decorator.

πŸŽ“ Lecture view vs reality

For the exam: a Chain link may handle and stop; a Decorator always delegates inward and adds on. In practice: those are the typical shapes, not hard rules. A Chain-of-Responsibility link can process the request and still pass it along (logging or enriching middleware does exactly this), and a Decorator can short-circuit β€” a caching Decorator returns early on a hit and never calls the wrappee. The dependable difference is intent: Chain is about finding the right handler; Decorator is about composing the full behaviour. Judge by intent when the flow-control clue is ambiguous.

Chain of Responsibility Β· Decorator.

11 Β· MVC vs 3-tier

A frequent mix-up because both name "three things." But they live at different levels: 3-tier is about physical/deployment tiers (where code runs), while MVC is a design pattern for organising UI code (how responsibilities split within a tier). They are not alternatives β€” you can build MVC inside the presentation tier of a 3-tier system.

Question3-TierMVC
LevelArchitectural / physical tiers.Design pattern for a codebase.
The three partsPresentation Β· Business logic Β· Data.Model Β· View Β· Controller.
Communication shapeLinear β€” everything passes through the middle tier; presentation never talks to data directly.Triangular β€” View β†’ Controller β†’ Model, and View reads from Model.
Separated byProcess / machine boundaries (can be deployed separately).Responsibility within (often) one process.
Question it answers"Where does each part run and scale?""How do I keep UI, logic, and data-shape from tangling?"
ℹ️ The lecture's one-liner

From the module slides: 3-tier is linear β€” all communication passes through the middle tier; MVC is triangular β€” the View updates the Controller, the Controller updates the Model, and the View reads from the Model. That linear-vs-triangular contrast is the fastest way to tell them apart.

πŸ’‘ Litmus

"Am I talking about where code is deployed, or how UI responsibilities are divided?" Deployment tiers β†’ 3-tier. Splitting UI/logic/data-representation in code β†’ MVC. If someone asks "which is better," it's a category error β€” they solve different problems and often coexist.

πŸŽ“ Lecture view vs reality

For the exam: a 3-tier system has presentation, business, and data tiers. In practice: keep tier and layer straight β€” a tier is a physical/deployment boundary (a separate process or machine you can scale independently), while a layer is a logical grouping of code that can all run in one process. "3-tier" implies the parts can be deployed separately; the same three-way split deployed in a single process is really "3-layer." MVC is a logical pattern too, so it lives happily inside the presentation tier. The linear-vs-triangular contrast is a handy clue, but the deciding question is still physical-deployment vs logical-organisation.

Full architectural treatment: 3-Tier and MVC in Chapter 8.

Edge-case quiz β€” "X or Y?"

Every question below is a deliberate edge case between two patterns from this page. Decide before you click, and say the litmus test out loud.

🧠 The one habit that fixes all of these

When two patterns look identical, stop staring at the class diagram and ask three questions: What is the intent? Who drives the change? Does the interface change / does the traversal stop? Structure is a coincidence; intent is the pattern. Come back here whenever two names start to blur β€” then confirm with the full chapters (3, 4, 5, 8) and test yourself in Chapter 13.