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.
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.
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.
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.
| Question | Strategy | State |
|---|---|---|
| Intent | Pick 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. |
| Lifetime | Typically set once and left alone. | Changes repeatedly over the object's life. |
| Clue in code | context.SetStrategy(new Quicksort()) called from outside. | context.State = new RedLight() called from inside a state's Handle. |
// 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
}
QuickSort object has no opinion about what runs next. It cannot reach the context to change anything.// 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."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.
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?
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.
| Adapter | Decorator | Proxy | Facade | Bridge | |
|---|---|---|---|---|---|
| Intent | Make 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 wrapped | Different β 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 change | Existing 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 code | Method 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
}
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
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();
}
}
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);
}
}
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.
Two things vary at the same time and you split them before writing subclasses? β Bridge.
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.
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.
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 Factory | Factory Method | Abstract Factory | Builder | |
|---|---|---|---|---|
| What it is | An 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 mechanism | A static method. | Inheritance β a virtual method + subclasses. | Composition β an interface with several create-methods. | Composition β a builder object with many setters + Build(). |
| Produces | One 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 by | A runtime argument. | Subclass identity. | Which concrete factory you injected. | The sequence of steps you call. |
| Clue in code | Create(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 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."
"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.
| Question | Observer | Mediator | Pub/Sub (architectural) |
|---|---|---|---|
| Shape | One 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. |
| Purpose | Broadcast a state change. | Untangle many-to-many chatter into a star. | Fully decouple senders from receivers across space/time. |
| Sync / async | Usually 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). |
| Level | GoF design pattern. | GoF design pattern. | Architectural pattern (message bus / event grid). |
| Clue in code | subject.Subscribe(obs); subject calls each observer. | Peers call mediator.Notify(this, evt). | bus.Publish("topic", msg); no receiver reference anywhere. |
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
}
}
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.
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.
"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.)
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).
| Question | Strategy | Command |
|---|---|---|
| Answers the question | "How should I do this step?" | "What action should happen (and when)?" |
| Grammar | An 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 code | result = strategy.Execute(input) returns a value. | command.Execute() causes a side-effect; often paired with Undo(). |
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);
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();
"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.
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).
| Question | Template Method | Strategy |
|---|---|---|
| Mechanism | Inheritance (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 principle | Yes β 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 code | protected abstract void Step(); called by a non-virtual template method. | A field IStrategy _s; plus a setter. |
"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.
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.
| Question | Composite | Decorator |
|---|---|---|
| Intent | Treat 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. |
| Shape | Tree. | Linked list / chain. |
| What it adds | Structure β grouping. | Behaviour β extra work around the same call. |
| Clue in code | List<IComponent> _children; iterated in operations. | Single IComponent _inner; field, delegated to then augmented. |
"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.)
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.
| Question | Iterator | Visitor |
|---|---|---|
| Owns | The 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. |
| Weakness | Doesn't help add operations. | Adding a new element type forces editing every visitor (double-dispatch cost). |
| Mechanism | MoveNext() / Current. | Double dispatch: element.Accept(visitor) β visitor.Visit(this). |
| Clue in code | Implements IEnumerator<T> / foreach. | Accept(IVisitor v) on elements; Visit(Circle), Visit(Square) on visitors. |
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.
"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.
| Question | Singleton (GoF) | static class | DI singleton lifetime |
|---|---|---|---|
| What it is | A 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. |
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.
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 */ }
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.
"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.
| Question | Chain of Responsibility | Decorator |
|---|---|---|
| Intent | Give 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 along | Find the right handler. | Compose the full behaviour. |
| Return | Often "handled / not handled." | The enriched result of the inner call. |
| Clue in code | if (CanHandle) {β¦; return;} else _next?.Handle(req); | var r = _inner.Do(); return r + extra; β always calls inner. |
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);
}
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
}
}
"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.
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.
| Question | 3-Tier | MVC |
|---|---|---|
| Level | Architectural / physical tiers. | Design pattern for a codebase. |
| The three parts | Presentation Β· Business logic Β· Data. | Model Β· View Β· Controller. |
| Communication shape | Linear β everything passes through the middle tier; presentation never talks to data directly. | Triangular β View β Controller β Model, and View reads from Model. |
| Separated by | Process / 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?" |
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.
"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.
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.
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.