Add code examples, and documentation

This commit is contained in:
Aaron Po
2026-08-12 00:03:32 -04:00
commit 77c16e69f6
79 changed files with 4458 additions and 0 deletions

84
docs/README.md Normal file
View File

@@ -0,0 +1,84 @@
# Design Patterns Cheatsheet
This repo implements seven patterns in C#, cross-referenced with _Design
Patterns: Elements of Reusable Object-Oriented Software_ (Gamma / Helm / Johnson
/ Vlissides — the "Gang of Four", **GoF**) and _Dive Into Design Patterns_
(Alexander Shvets, 2019).
Every code sample comes from the actual project files. Each **Output** block
reflects a real build and run of that project — none of the output is
hand-traced.
## The patterns
**Structural** — how to assemble objects and classes into larger structures.
| Pattern | One-line idea | Repo project |
| ------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------ |
| [Bridge](bridge.md) | Splits one exploding hierarchy into two, joined by composition. | `patterns/bridge/Bridge Example/` |
| [Decorator](decorator.md) | Stacks wrappers, each adding one behavior, all sharing an interface. | `patterns/decorator/ShapeDecorations/` |
| [Facade](facade.md) | Hides a pile of fiddly subsystem classes behind one friendly class. | `patterns/facade/CarEngineFacade/` (+ `CarEngineFacadeRecording/`) |
**Behavioral** — how objects communicate and divide responsibility.
| Pattern | One-line idea | Repo project |
| ----------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| [Mediator](mediator.md) | Stops objects from calling each other directly; routes every call through one go-between. | `patterns/mediator/AirTrafficControl/` |
| [Memento](memento.md) | Lets an object snapshot itself, opaquely, for undo. | `patterns/memento/MementoPattern/` |
| [Observer](observer.md) | Publishes changes to a dynamic list of subscribed listeners. | `patterns/observer/ObserverPattern/` (+ `UpdatedOne/`) |
| [State](state.md) | Replaces a giant `switch` on a state field with one class per state. | `patterns/state/BadCarState/` + `CarState/` (+ `WithStatePattern/`, `WithoutStatePattern/`) |
## Also in here
- **[Cross-pattern comparisons](cross-pattern-comparisons.md)** — the
distinctions most likely to be tested and most likely to cause confusion in
review, such as Bridge vs. State vs. Strategy, Decorator vs. Proxy vs.
Adapter, and Facade vs. Mediator vs. Observer.
- **[Principles these examples keep demonstrating](principles.md)** —
encapsulate what varies, program to an interface, favor composition, the
Single Responsibility Principle, and the Open/Closed Principle.
## Quick reference: GoF intents (verbatim)
| Pattern | GoF Intent | Key structural move |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| **Bridge** | "Decouple an abstraction from its implementation so that the two can vary independently." | Abstraction _has-a_ Implementor |
| **Decorator** | "Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality." | Wrapper _is-a_ AND _has-a_ Component |
| **Facade** | "Provide a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use." | Facade _has-many_ subsystem parts |
| **Mediator** | "Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently." | N×N links → N links to a hub |
| **Memento** | "Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later." | Nested private snapshot class |
| **Observer** | "Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically." | Subject holds `List<IObserver>` |
| **State** | "Allow an object to alter its behavior when its internal state changes. The object will appear to change its class." | Context delegates to a swappable State |
## Before / after pair
One pattern ships with a deliberately bad version alongside the patterned
version. Diffing that pair teaches more than either version alone, so both are
reproduced in full:
- **[State](state.md)** — `BadCarState` (a `switch` inside a `switch`, four
times over) vs. `CarState` (one class per state). The two produce
**byte-identical** output, confirming the refactor changed structure and
nothing else.
## Run these projects
All projects target `net10.0` and are wired into the root `DesignPatterns.slnx`.
Build and run any project directly with the .NET 10 SDK:
```bash
dotnet run --project "patterns/bridge/Bridge Example/Bridge Example.csproj"
```
`CarState`, `BadCarState`, `WithStatePattern`, `WithoutStatePattern`, and
`MementoPattern` read from stdin. Pipe input to them:
```bash
printf 'gas\nturn-clockwise\ngas\nbrake\nturn-counter\nexit\n' | dotnet run --project patterns/state/CarState/CarState.csproj
```
Each `patterns/<pattern>/` folder also holds any in-class recording or
before/after variant alongside the canonical example — for example,
`patterns/facade/CarEngineFacadeRecording/`. The `exercises/` folder holds
material that doesn't map to a specific GoF pattern, such as `ClassIntroExam`, a
pass-by-value-vs-reference demo.

205
docs/bridge.md Normal file
View File

@@ -0,0 +1,205 @@
[← Design patterns index](README.md)
# Bridge
**Category:** Structural | **Source:** `patterns/bridge/Bridge Example/`
## Problem
A `Notification` hierarchy (`Alert`, `Reminder`) needs a second, independent
dimension — delivery channel (`Email`, `SMS`). Subclassing both at once yields
`EmailAlertNotification`, `SmsReminderNotification`, … and the count grows
geometrically: each new notification type needs one subclass _per existing
channel_, and each new channel needs one _per existing notification type_.
## Solution
Stop expressing both dimensions with inheritance. Keep one as a class hierarchy
(the **Abstraction**), extract the other into its own hierarchy (the
**Implementation**), and join them by composition — the abstraction holds a
reference to an implementor and delegates the real work to it. Each hierarchy
now grows independently, and the implementation is swappable at runtime.
> The GoF words "Abstraction" and "Implementation" do **not** mean your
> language's `abstract`/`interface` keywords. They mean "the control-facing
> half" and "the platform-facing half" of a split. (_Dive Into Design Patterns_,
> p.165)
## Participants (GoF names → this repo)
- **Abstraction** → `Notification` — defines the client-facing interface, holds
an `IMessageSender`.
- **RefinedAbstraction** → `AlertNotification`, `ReminderNotification` —
variants of control logic.
- **Implementor** → `IMessageSender` — primitive operations; need not mirror the
Abstraction's interface.
- **ConcreteImplementor** → `EmailSender`, `SmsSender` — the channel-specific
code.
```mermaid
classDiagram
class Notification {
<<abstract>>
#IMessageSender _sender
+Notify(message)*
}
class IMessageSender {
<<interface>>
+SendMessage(message)
}
Notification o--> IMessageSender : THE BRIDGE - delegates to
Notification <|-- AlertNotification
Notification <|-- ReminderNotification
IMessageSender <|.. EmailSender
IMessageSender <|.. SmsSender
```
The single `o-->` arrow _is_ the bridge. Left of it, control logic grows freely;
right of it, platforms grow freely; neither side multiplies the other.
## Code
```csharp
// IMessageSender.cs — Implementor
public interface IMessageSender
{
void SendMessage(string message);
}
// EmailSender.cs — ConcreteImplementor
public class EmailSender : IMessageSender
{
public void SendMessage(string message)
{
Console.WriteLine($"Email sent: {message}");
}
}
// SmsSender.cs — ConcreteImplementor
public class SmsSender : IMessageSender
{
public void SendMessage(string message)
{
Console.WriteLine($"SMS sent: {message}");
}
}
// Notification.cs — Abstraction
public abstract class Notification
{
protected readonly IMessageSender _sender;
protected Notification(IMessageSender sender)
{
_sender = sender; // the bridge is established here, at construction
}
public abstract void Notify(string message);
}
// AlertNotification.cs — RefinedAbstraction (ReminderNotification is identical but for its prefix)
public class AlertNotification : Notification
{
public AlertNotification(IMessageSender sender) : base(sender) { }
public override void Notify(string message)
{
_sender.SendMessage($"ALERT: {message}"); // high-level op built from the primitive
}
}
```
```csharp
// Program.cs — the client picks both halves independently, at runtime
Notification n1 = new AlertNotification(new EmailSender());
Notification n2 = new AlertNotification(new SmsSender());
Notification n3 = new ReminderNotification(new EmailSender());
Notification n4 = new ReminderNotification(new SmsSender());
n1.Notify("Server is down");
n2.Notify("Server is down");
n3.Notify("Meeting at 3pm");
n4.Notify("Meeting at 3pm");
```
The payoff: at no point is there an `EmailAlertNotification` class or an
`SmsReminderNotification` class — the notification type and the delivery channel
bind together at runtime, not compile time. 2 abstractions × 2 implementors = 4
usable combinations from **4** classes, not 4 combination classes. At 5 × 5 it
would be 10 classes instead of 25.
## Output (verified)
```
Email sent: ALERT: Server is down
SMS sent: ALERT: Server is down
Email sent: Reminder: Meeting at 3pm
SMS sent: Reminder: Meeting at 3pm
```
## GoF pseudocode (remote controls × devices)
```
class RemoteControl is
protected field device: Device
constructor RemoteControl(device: Device) is this.device = device
method togglePower() is
if (device.isEnabled()) then device.disable() else device.enable()
method volumeUp() is
device.setVolume(device.getVolume() + 10) // high-level op from primitives
class AdvancedRemoteControl extends RemoteControl is
method mute() is device.setVolume(0)
interface Device is
method isEnabled() method enable() method disable()
method getVolume() method setVolume(percent)
class Tv implements Device is // ...
class Radio implements Device is // ...
tv = new Tv()
remote = new RemoteControl(tv)
remote.togglePower()
```
## Applicability
- You want to avoid permanently binding an abstraction to one implementation —
for example, when the implementation must be selected or switched at run-time.
- Both abstraction and implementation should be extensible by subclassing,
independently of each other.
- Changes to an implementation must not force clients to recompile.
- You already have a proliferation of combination classes (GoF calls this smell
"nested generalizations").
- You want to share one implementor among several abstraction objects, invisibly
to the client.
## Pros / Cons
| Pros | Cons |
| -------------------------------------------------------------------- | --------------------------------------------------------------- |
| Platform-independent classes; implementor swappable at runtime | Can over-complicate a class that was already small and cohesive |
| Client only ever sees the high-level abstraction | |
| Open/Closed — add abstractions or implementors independently | |
| Single Responsibility — control logic vs. platform detail live apart | |
## Seen in the wild (.NET)
- ADO.NET — `DbConnection`/`DbCommand` as abstraction, per-provider drivers as
implementors.
- `ILogger` as an abstraction over pluggable logging providers.
- Any `HttpMessageHandler` swapped into `HttpClient`.
## Related patterns
- **Adapter** is retrofitted onto existing incompatible classes; Bridge is
designed up front.
- **Bridge / State / Strategy** share the "delegate to a composed object" shape
but differ in intent — the name still communicates the problem being solved.
- **Abstract Factory** pairs well when only certain abstraction/implementor
combinations are legal.
---
[All patterns](README.md) · **Next →** [Decorator](decorator.md)

View File

@@ -0,0 +1,20 @@
[← Design patterns index](README.md)
# Cross-pattern comparisons worth memorizing
These are the distinctions most likely to be tested, and the ones that actually
bite in review:
| Confusion | The tell |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Bridge vs. State vs. Strategy** | Near-identical UML (context → interface, delegate). Bridge: two hierarchies designed up front, both varying. State: the delegate swaps _itself_ and drives transitions. Strategy: interchangeable, mutually-unaware algorithms chosen by the client. |
| **Decorator vs. Proxy vs. Adapter** | All wrap one object. Adapter **changes** the interface. Decorator **keeps** it and adds behavior, client-controlled and stackable. Proxy **keeps** it but controls access/lifetime and manages its own subject. |
| **Decorator vs. Composite** | Decorator has exactly one child and _adds_ responsibility; Composite has many and _aggregates_ their results. |
| **Facade vs. Mediator** | Direction. Facade is one-way and the subsystem doesn't know it exists. Mediator is many-way and colleagues hold references to it. |
| **Mediator vs. Observer** | Mediator removes mutual dependencies among a known set of components via a hub. Observer creates dynamic, independently-subscribable one-way links. Often combined: mediator-as-publisher. |
| **Memento vs. Command** | Not rivals — partners. Command = "the operation"; Memento = "the state to restore if it's undone." |
| **Facade vs. Abstract Factory** | Both hide a subsystem. Abstract Factory hides only _creation_; Facade hides creation **and** call sequencing. |
---
[All patterns](README.md)

253
docs/decorator.md Normal file
View File

@@ -0,0 +1,253 @@
[← Design patterns index](README.md)
# Decorator
**Category:** Structural | **Also known as: Wrapper** | **Source:**
`patterns/decorator/ShapeDecorations/`
## Problem
You want to add behavior to _individual objects_ at runtime, not to every
instance of a class, without an explosion of subclasses for every combination
(`BorderedScrollableTextView`, `BorderedTextView`, …). Inheritance is also
static: once an object is constructed you cannot add or remove an inherited
behavior.
## Solution
Wrap the object in a decorator that **implements the same interface** and
**holds a reference** to what it wraps. The decorator delegates to the wrappee
and adds its own behavior before or after. Because a decorator is itself the
component type, decorators wrap decorators — you build a stack, and the
outermost object stays indistinguishable from the original to every caller.
## Participants (GoF names → this repo)
- **Component** → `IShape`
- **ConcreteComponent** → `Circle`, `Rectangle`
- **Decorator** → `ShapeDecorator` (abstract base holding the wrappee)
- **ConcreteDecorator** → `RedColor`, `BlueBorder`, `HugeSize`
```mermaid
classDiagram
class IShape {
<<interface>>
+Draw()
}
class ShapeDecorator {
<<abstract>>
#IShape _decorated
+Draw()*
}
IShape <|.. Circle
IShape <|.. Rectangle
IShape <|.. ShapeDecorator
ShapeDecorator o--> IShape : wraps - recursive
ShapeDecorator <|-- RedColor
ShapeDecorator <|-- BlueBorder
ShapeDecorator <|-- HugeSize
```
`ShapeDecorator` both **implements** `IShape` and **holds** an `IShape`. That
double relationship is the whole pattern — it is what makes wrapping recursive.
## Code
```csharp
// IShape.cs — Component
public interface IShape
{
void Draw();
}
// Circle.cs / Rectangle.cs — ConcreteComponents (the only classes that can start a stack)
public class Circle : IShape
{
public void Draw() { Console.WriteLine("Drawing a circle"); }
}
public class Rectangle : IShape
{
public void Draw() { Console.WriteLine("Drawing a Rectangle"); }
}
```
```csharp
// ShapeDecorator.cs — base Decorator (doc comments condensed from the original)
public abstract class ShapeDecorator : IShape
{
/// Maintains a reference to the shape which it decorates.
/// Marked "protected" so that inheriting classes have access to it.
protected IShape _decorated;
/// Accepts a reference to any IShape. Since the ShapeDecorator itself IS an
/// IShape, this decorator can also decorate other decorators. However, since a
/// ShapeDecorator has to be instantiated with an existing IShape, at some point
/// in the chain there must be a concrete shape.
public ShapeDecorator(IShape decorated)
{
_decorated = decorated;
}
/// Abstract: satisfies IShape, but forces subclasses to write their own logic.
public abstract void Draw();
}
```
```csharp
// RedColor.cs — ConcreteDecorator (BlueBorder and HugeSize are the same shape)
public class RedColor : ShapeDecorator
{
public RedColor(IShape decorated) : base(decorated) { }
public override void Draw()
{
_decorated.Draw(); // 1. delegate inward
Console.WriteLine("\tWhich is red in color"); // 2. then add own behavior
}
}
// BlueBorder.Draw() -> _decorated.Draw(); "\tWith a blue border"
// HugeSize.Draw() -> _decorated.Draw(); "\tThat is enourmous in size"
```
```csharp
// Program.cs — client assembles the stack; each variable remains an IShape
var circle = new Circle();
circle.Draw();
var red = new RedColor(circle); // Circle → RedColor
red.Draw();
var blueBorder = new BlueBorder(red); // Circle → RedColor → BlueBorder
blueBorder.Draw();
var huge = new HugeSize(blueBorder); // Circle → RedColor → BlueBorder → HugeSize
huge.Draw();
// Second block: same decorators, different order, different concrete component
var rect = new Rectangle();
var blueBorder2 = new BlueBorder(rect);
var huge2 = new HugeSize(blueBorder2);
var red2 = new RedColor(huge2);
red2.Draw();
```
## Output (verified)
```
Drawing a circle
Drawing a circle
Which is red in color
Drawing a circle
Which is red in color
With a blue border
Drawing a circle
Which is red in color
With a blue border
That is enourmous in size
Drawing a Rectangle
Drawing a Rectangle
With a blue border
Drawing a Rectangle
With a blue border
That is enourmous in size
Drawing a Rectangle
With a blue border
That is enourmous in size
Which is red in color
```
Read the output as the call stack unwinding: because every decorator calls
`_decorated.Draw()` **first**, the innermost concrete shape prints first and
decorations print outward in wrapping order. The `Program.cs` comment says _"the
order in which we decorate doesn't matter"_ — true for _these_ decorators,
because each only appends an independent line. It is **not** true in general
(see Cons).
## GoF pseudocode (compression + encryption over a data source)
```
interface DataSource is
method writeData(data)
method readData(): data
class FileDataSource implements DataSource is // ConcreteComponent
method writeData(data) is // write to file
method readData(): data is // read from file
class DataSourceDecorator implements DataSource is // base Decorator
protected field wrappee: DataSource
constructor DataSourceDecorator(source) is wrappee = source
method writeData(data) is wrappee.writeData(data)
method readData(): data is return wrappee.readData()
class EncryptionDecorator extends DataSourceDecorator is
method writeData(data) is // encrypt, then wrappee.writeData(...)
method readData(): data is // wrappee.readData(), then decrypt
class CompressionDecorator extends DataSourceDecorator is
method writeData(data) is // compress, then wrappee.writeData(...)
method readData(): data is // wrappee.readData(), then decompress
// the stack is assembled from configuration, at runtime
source = new FileDataSource("salary.dat")
if (enabledEncryption) source = new EncryptionDecorator(source)
if (enabledCompression) source = new CompressionDecorator(source)
```
Note this example's decorators _are_ order-sensitive: compress-then-encrypt and
encrypt-then-compress give different bytes (and wildly different compression
ratios).
## Applicability
- Add responsibilities to individual objects dynamically and transparently,
without affecting other objects.
- The responsibilities can be withdrawn again.
- Extension by subclassing is impractical: too many independent combinations, or
the class is `sealed`/`final` and can't be subclassed at all.
## Pros / Cons
| Pros | Cons |
| ----------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Extend behavior with no new subclass | Hard to remove one specific wrapper from the middle of a stack |
| Add/remove responsibilities at runtime | Hard to keep behavior independent of stack order |
| Combine behaviors by stacking | Setup code that assembles the stack gets ugly |
| Single Responsibility — one class per behavior | A decorated object is **not** identical to the original — don't rely on reference identity or `GetType()` |
| Easy to apply the _same_ decorator twice (for example, a double border) | Yields lots of small look-alike objects; harder to debug |
## Seen in the wild (.NET)
- `System.IO.Stream` is the canonical case: `GZipStream`, `CryptoStream`, and
`BufferedStream` each wrap another `Stream` and are themselves a `Stream` —
exactly the GoF pseudocode shown earlier.
- ASP.NET Core middleware.
- `IMemoryCache`/`HttpMessageHandler` wrappers.
## Related patterns
- **Adapter** changes an interface; Decorator keeps it and adds behavior — and
supports recursive composition, which Adapter doesn't.
- **Composite** — similar recursive-composition diagram, but a Decorator has
exactly one child and _adds responsibilities_, while Composite _sums up_ many
children. You can decorate a single node inside a Composite tree.
- **Strategy** — Decorator changes an object's _skin_; Strategy changes its
_guts_.
- **Proxy** — same structure, different intent: a Proxy manages its subject's
lifetime and access; a Decorator's composition is always driven by the client.
- **Chain of Responsibility** — also recursive, but handlers may _stop_ the
request; decorators must never break the flow.
---
**← Prev:** [Bridge](bridge.md) · [All patterns](README.md) · **Next →**
[Facade](facade.md)

177
docs/facade.md Normal file
View File

@@ -0,0 +1,177 @@
[← Design patterns index](README.md)
# Facade
**Category:** Structural | **Source:** `patterns/facade/CarEngineFacade/` (+
`CarEngineFacadeRecording/`)
## Problem
Using a complex subsystem directly means the client must know every class,
instantiate them all, and call their methods **in the correct order**. That
sequencing knowledge leaks into — and couples — every piece of business logic
that touches the subsystem.
## Solution
Add one class that owns the wiring and the call sequence, exposing a couple of
high-level methods. The subsystem classes stay completely unaware the facade
exists. A facade adds **no new functionality**; it only hides complexity,
deliberately trading away some of the subsystem's generality for ease of use.
## Participants (GoF names → this repo)
- **Facade** → `Facade` — knows which subsystem class handles what, and in what
order.
- **Subsystem classes** → `AirFlowController`, `FuelInjector`, `Starter`,
`CoolingController`, `CatalyticConverter` — do the work, hold no reference to
the facade.
```mermaid
classDiagram
class Facade {
-AirFlowController _air
-FuelInjector _fuel
-Starter _start
-CoolingController _cool
-CatalyticConverter _cat
+Start()
+Stop()
}
Program --> Facade : only dependency
Facade --> AirFlowController
Facade --> FuelInjector
Facade --> Starter
Facade --> CoolingController
Facade --> CatalyticConverter
```
Note every arrow points _away_ from the facade. Nothing points back — that
one-directionality is what distinguishes Facade from Mediator.
## Code
```csharp
// The five subsystem classes — real project has empty "// implementation" bodies
public class AirFlowController { public void TakeAir() {} public void Off() {} }
public class FuelInjector { public void On() {} public void Inject() {} public void Off() {} }
public class Starter { public void Start() {} }
public class CoolingController { public void Run() {} public void Stop() {} public void Cool() {} }
public class CatalyticConverter { public void On() {} public void Off() {} }
```
```csharp
// Facade.cs — owns construction AND ordering
public class Facade
{
private readonly AirFlowController _air = new();
private readonly CatalyticConverter _cat = new();
private readonly CoolingController _cool = new();
private readonly FuelInjector _fuel = new();
private readonly Starter _start = new();
public void Start()
{
_air.TakeAir(); // order matters: air before fuel before spark
_fuel.On();
_fuel.Inject();
_start.Start();
_cool.Run();
_cat.On();
}
public void Stop()
{
_fuel.Off(); // NOT the reverse of Start() — shutdown has its own order
_cat.Off();
_cool.Cool(); // keep cooling after fuel is cut, then stop cooling
_cool.Stop();
_air.Off();
}
}
```
```csharp
// Program.cs — the file keeps the "before" version commented out for comparison
// WITHOUT the facade — client owns construction AND sequencing:
// var airflow = new AirFlowController();
// var fuel = new FuelInjector();
// var start = new Starter();
// var cool = new CoolingController();
// var cat = new CatalyticConverter();
// airflow.TakeAir(); fuel.On(); fuel.Inject(); start.Start(); cool.Run(); cat.On();
// fuel.Off(); cat.Off(); cool.Cool(); cool.Stop(); airflow.Off();
// WITH the facade — 11 ordered calls across 5 types collapse to 2 calls on 1 type:
var facade = new Facade();
facade.Start();
facade.Stop();
```
That the `Stop()` sequence is _not_ `Start()` reversed is the point of the whole
example: the ordering is real domain knowledge, it is easy to get wrong, and it
should live in exactly one place.
## Output (verified)
**None** — the subsystem method bodies are deliberately empty stubs, so the
program runs and exits silently. This example is about the _shape of the
dependencies_, not runtime behavior.
## GoF pseudocode (video conversion library)
```
class VideoConverter is
method convert(filename, format): File is
file = new VideoFile(filename)
sourceCodec = new CodecFactory.extract(file)
if (format == "mp4") destinationCodec = new MPEG4CompressionCodec()
else destinationCodec = new OggCompressionCodec()
buffer = BitrateReader.read(filename, sourceCodec)
result = BitrateReader.convert(buffer, destinationCodec)
result = (new AudioMixer()).fix(result)
return new File(result)
convertor = new VideoConverter()
mp4 = convertor.convert("youtubevideo.ogg", "mp4")
```
## Applicability
- You want a simple, limited interface to a complex subsystem that satisfies
most clients; the few needing more can still reach past the facade.
- There are many dependencies between clients and a subsystem's implementation
classes.
- You want to **layer** subsystems — give each layer a facade and let layers
talk only through them.
## Pros / Cons
| Pros | Cons |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| Isolates client code from subsystem complexity | Can grow into a god object coupled to everything |
| Reduces the number of objects clients deal with; cuts compile-time dependencies | Adds an indirection layer that must be maintained |
| Doesn't _prevent_ direct subsystem access when a client needs it | A facade that grows too large needs splitting into refined facades |
## Seen in the wild (.NET)
- `HttpClient` over the sockets/handler/header stack.
- `File.ReadAllText` over `FileStream` + `StreamReader` + encoding detection.
- `WebApplication.CreateBuilder` over the hosting/DI/config subsystems.
## Related patterns
- **Adapter** makes an _existing_ interface usable; Facade defines a _new_,
simpler one. Adapter usually wraps one object, Facade a whole subsystem.
- **Facade vs. Mediator** — both organize collaboration among many classes.
Facade is **one-directional** and the subsystem doesn't know it exists;
Mediator is **multi-directional** and colleagues explicitly depend on it.
- **Abstract Factory** is the alternative when you only want to hide subsystem
_object creation_.
- A facade is often made a **Singleton**, since one instance usually suffices.
---
**← Prev:** [Decorator](decorator.md) · [All patterns](README.md) · **Next →**
[Mediator](mediator.md)

284
docs/mediator.md Normal file
View File

@@ -0,0 +1,284 @@
[← Design patterns index](README.md)
# Mediator
**Category:** Behavioral | **Also known as: Intermediary, Controller** |
**Source:** `patterns/mediator/AirTrafficControl/`
## Problem
An airport tower needs to arbitrate five runways among any number of flights
and ground vehicles. Wire that directly, and each `Flight` and `GroundVehicle`
needs its own reference to the runway array and to every other aircraft
currently occupying a runway. Every colleague then duplicates the same
allocation logic. A new colleague type, such as a snowplow, needs the same
wiring again.
## Solution
Give every colleague exactly one reference: to a **mediator**. Colleagues stop
tracking runway state themselves — they send a request (`RequestLanding`,
`RequestTakeoff`, `RequestRunwayCrossing`, …) to the mediator and wait to be
told `accepted: true/false`. The mediator alone owns the five `Runway` slots
and decides who gets a runway. Colleagues never learn about each other at all.
## Participants (GoF names → this repo)
- **Mediator** → `IMediator` — declares `Notify(sender, request)`.
- **ConcreteMediator** → `AirTrafficControlTower` — owns the five runways and
is the only class that knows the allocation rules.
- **Colleague** → `IColleague` — declares
`ReceiveMessage(instruction, accepted)`.
- **ConcreteColleague** → `Flight`, `GroundVehicle` — send requests, receive
accept/reject callbacks; neither knows the other exists.
```mermaid
classDiagram
class IMediator {
<<interface>>
+Notify(sender, request)
}
class IColleague {
<<interface>>
+ReceiveMessage(instruction, accepted)
}
class AirTrafficControlTower {
-Runway[5] _runways
+Notify(sender, request)
}
class Flight {
+RequestLanding()
+RequestTakeoff()
}
class GroundVehicle {
+RequestRunwayCrossing()
}
IMediator <|.. AirTrafficControlTower
IColleague <|.. Flight
IColleague <|.. GroundVehicle
Flight --> IMediator : Notify(this, request)
GroundVehicle --> IMediator : Notify(this, request)
AirTrafficControlTower --> IColleague : ReceiveMessage(instruction, accepted)
```
Note the arrow direction: `Flight`/`GroundVehicle` only ever point at
`IMediator`; `AirTrafficControlTower` only ever points back at `IColleague`.
Neither colleague type points at the other.
## Code
```csharp
// Program.cs — Mediator + Colleague interfaces
public interface IColleague
{
void ReceiveMessage(string instruction, bool accepted);
}
public interface IMediator
{
void Notify(IColleague sender, AirTrafficRequest request);
}
// Flight — ConcreteColleague: only ever talks to _mediator
public class Flight : IColleague
{
private readonly IMediator _mediator;
public string FlightNumber { get; }
public Flight(string flightNumber, IMediator mediator)
{
FlightNumber = flightNumber;
_mediator = mediator;
}
public void RequestLanding() =>
_mediator.Notify(this, AirTrafficRequest.LandingRequest);
public void LandingCompleted() =>
_mediator.Notify(this, AirTrafficRequest.LandingRequestDone);
public void ReceiveMessage(string instruction, bool accepted) =>
Console.WriteLine(accepted
? $"Flight {FlightNumber} received instruction: {instruction}"
: $"Flight {FlightNumber} received rejection for instruction: {instruction}");
// RequestTakeoff() / TakeoffCompleted() follow the same shape
}
// GroundVehicle mirrors Flight exactly, but for RequestRunwayCrossing() /
// RunwayCrossingCompleted() — same interface, same "only knows the mediator" shape
// AirTrafficControlTower — ConcreteMediator: the only class that knows
// about runways and occupancy
public class AirTrafficControlTower : IMediator
{
private class Runway
{
public string Name { get; init; } = "";
public IColleague? OccupiedBy { get; set; }
public Runway(string name) => Name = name;
}
private readonly Runway[] _runways = new Runway[5];
public AirTrafficControlTower()
{
for (int i = 0; i < _runways.Length; i++)
_runways[i] = new Runway($"Runway R{i + 1}");
}
public void Notify(IColleague sender, AirTrafficRequest request)
{
switch (request)
{
case AirTrafficRequest.LandingRequest: HandleLandingRequest(sender); break;
case AirTrafficRequest.TakeoffRequest: HandleTakeoffRequest(sender); break;
case AirTrafficRequest.RunwayCrossingRequest: HandleRunwayCrossingRequest(sender); break;
case AirTrafficRequest.LandingRequestDone: HandleLandingRequestDone(sender); break;
case AirTrafficRequest.TakeoffRequestDone: HandleTakeoffRequestDone(sender); break;
case AirTrafficRequest.RunwayCrossingRequestDone: HandleRunwayCrossingRequestDone(sender); break;
}
}
void HandleLandingRequest(IColleague sender)
{
for (int i = 0; i < _runways.Length; i++)
{
if (_runways[i].OccupiedBy is null)
{
_runways[i].OccupiedBy = sender;
sender.ReceiveMessage($"Landing approved on {_runways[i].Name}.", accepted: true);
return;
}
}
sender.ReceiveMessage("No available runways for landing.", accepted: false);
}
// HandleTakeoffRequest / HandleRunwayCrossingRequest mirror
// HandleLandingRequest; the three Handle*Done methods clear whichever
// runway `sender` occupies
}
```
```csharp
// Program.cs — client: flat construction, then requests through the tower only
var tower = new AirTrafficControlTower();
var flight1 = new Flight("AC101", tower);
var vehicle1 = new GroundVehicle("V1", tower);
flight1.RequestLanding(); // approved: Runway R1 free
// ... 4 more requests fill R2–R5 ...
vehicle2.RequestRunwayCrossing(); // rejected: no runways left
flight1.LandingCompleted(); // frees R1
vehicle2.RequestRunwayCrossing(); // now approved, on the freed R1
```
## Output (verified)
```
--- Occupying all 5 runways ---
Flight AC101 requesting landing.
Flight AC101 received instruction: Landing approved on Runway R1.
Flight WJ202 requesting takeoff.
Flight WJ202 received instruction: Takeoff approved on Runway R2.
Flight DL303 requesting landing.
Flight DL303 received instruction: Landing approved on Runway R3.
Flight UA404 requesting takeoff.
Flight UA404 received instruction: Takeoff approved on Runway R4.
Ground vehicle V1 requesting runway crossing.
Ground vehicle V1 received instruction: Crossing approved on Runway R5.
--- No runways left: this request should be rejected ---
Ground vehicle V2 requesting runway crossing.
Ground vehicle V2 received rejection for instruction: No available runways for crossing.
--- Freeing up a runway: the previously rejected request can now succeed ---
Flight AC101 has completed landing.
Flight AC101 received instruction: You have cleared Runway R1 after landing.
Ground vehicle V2 requesting runway crossing.
Ground vehicle V2 received instruction: Crossing approved on Runway R1.
--- Clearing the remaining runways ---
Flight WJ202 has completed takeoff.
Flight WJ202 received instruction: You have cleared Runway R2 after takeoff.
Flight DL303 has completed landing.
Flight DL303 received instruction: You have cleared Runway R3 after landing.
Flight UA404 has completed takeoff.
Flight UA404 received instruction: You have cleared Runway R4 after takeoff.
Ground vehicle V1 has completed runway crossing.
Ground vehicle V1 received instruction: You have cleared Runway R5 after crossing.
Ground vehicle V2 has completed runway crossing.
Ground vehicle V2 received instruction: You have cleared Runway R1 after crossing.
```
## GoF pseudocode (dialog coordinating its form controls)
```
interface Mediator is
method notify(sender: Component, event: string)
class AuthenticationDialog implements Mediator is
field loginOrRegisterChkBx: Checkbox
field okBtn: Button
method notify(sender, event) is
if (sender == loginOrRegisterChkBx and event == "check")
// show/hide login vs. registration fields, retitle the dialog
if (sender == okBtn and event == "click")
// validate credentials or create the account
class Component is
field dialog: Mediator
constructor Component(dialog) is this.dialog = dialog
method click() is dialog.notify(this, "click")
class Checkbox extends Component is
method check() is dialog.notify(this, "check")
```
This repo's version tracks the canonical shape closely:
`Notify(sender, request)` takes the same two arguments as GoF's
`notify(sender, event)` — only an `AirTrafficRequest` enum in place of a bare
string, and an `IMediator` interface, so colleagues aren't wired to one concrete
mediator class. Where the GoF sketch dispatches on `sender` identity inside a
single `notify`, this repo dispatches on the request type and lets five
`Handle*` methods each scan `_runways` — same idea, more moving parts because
there are more request kinds than the two-widget dialog example needs.
## Applicability
- A set of objects communicate in well-defined but complex ways, and the
resulting interdependencies are unstructured and hard to follow.
- Reusing an object is hard because it refers to and talks to many others.
- Behavior distributed across several classes should be customizable without
lots of subclassing — write a new mediator instead.
## Pros / Cons
| Pros | Cons |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| Single Responsibility — collaboration logic sits in one place | The mediator can become a God Object / monolith |
| Open/Closed — new mediators without touching components | It centralizes control: complexity moves rather than vanishes |
| Components decouple and become individually reusable | Five near-identical `Handle*` methods here already show the mediator absorbing complexity the colleagues no longer carry |
| Replaces many-to-many interactions with one-to-many | |
## Seen in the wild (.NET)
- WinForms/WPF code-behind acting as dialog director.
- The MediatR library.
- SignalR hubs routing between clients.
- A real air-traffic-control tower — this repo's own example, and the book's
analogy: pilots never talk to each other, only to the tower.
## Related patterns
- **Chain of Responsibility / Command / Mediator / Observer** are the four ways
to connect senders and receivers: CoR passes along a chain until handled;
Command makes a one-way sender→receiver link; **Mediator removes direct links,
forcing a hub**; Observer lets receivers subscribe dynamically.
- **Facade vs. Mediator** — directionality (see Facade).
- **Mediator vs. Observer** — genuinely blurry. Distinguishing goal: Mediator
eliminates mutual dependencies among a known set of components by routing
every interaction through a fixed hub; Observer establishes dynamic one-way
subscriptions where any number of unknown subscribers can attach later.
---
**← Prev:** [Facade](facade.md) · [All patterns](README.md) · **Next →**
[Memento](memento.md)

275
docs/memento.md Normal file
View File

@@ -0,0 +1,275 @@
[← Design patterns index](README.md)
# Memento
**Category:** Behavioral | **Also known as: Snapshot** | **Source:**
`patterns/memento/MementoPattern/`
## Problem
You want undo. The obvious approach — some other object reads the target's
fields and stores copies — only works if the target exposes its state publicly.
That breaks encapsulation, and every later refactor of the target ripples out
into whatever was copying its fields.
## Solution
Let the state's owner (the **Originator**) produce its own opaque snapshot (the
**Memento**) and restore itself from one later. A **Caretaker** holds mementos —
in an undo stack — without being able to read or alter them. GoF calls this the
**narrow vs. wide interface**: the caretaker sees a narrow interface (it can
only hold and pass the memento along); the originator sees a wide one (full
access to restore itself).
In languages with nested classes — "C++, C#, and Java", as _Dive Into Design
Patterns_ notes — the classic implementation nests the memento **inside** the
originator, which is exactly what this project does.
## Participants (GoF names → this repo)
- **Originator** → `Originator` (implements `IOriginator`)
- **Memento** → `Originator.Memento`, a **private nested class** implementing
`IMemento`
- **Caretaker** → `Caretaker`, holding a `Stack<IMemento>`
```mermaid
classDiagram
class IMemento {
<<interface>>
declaresNothing_theNarrowInterface
}
class IOriginator {
<<interface>>
+GetMemento() IMemento
+SetMemento(IMemento)
}
class Originator {
-Guid _id
+GetMemento() IMemento
+SetMemento(IMemento)
+UpdateId()
}
class Memento {
<<private nested>>
+Guid Id
}
class Caretaker {
-Stack~IMemento~ _mementos
-IOriginator _originator
+Save()
+Undo()
}
IOriginator <|.. Originator
Originator *-- Memento : nested, private
IMemento <|.. Memento
Caretaker --> IOriginator
Caretaker --> IMemento : holds, cannot read
```
## Code
```csharp
// IMemento.cs — the narrow interface. It declares NOTHING, on purpose:
// that is precisely what stops the Caretaker from reading the snapshot.
public interface IMemento
{
}
// IOriginator.cs
public interface IOriginator
{
IMemento GetMemento();
void SetMemento(IMemento memento);
}
```
```csharp
// Originator.cs — Console.WriteLine calls kept, since the verified output depends on them
public class Originator : IOriginator
{
// Private + nested: only Originator can even name this type, let alone read .Id
private class Memento : IMemento
{
public Guid Id;
}
private Guid _id;
public Originator()
{
_id = Guid.NewGuid();
Console.WriteLine($"Starting ID: {_id}");
}
public IMemento GetMemento()
{
Memento memento = new Memento();
memento.Id = _id;
Console.WriteLine("Creating Memento...");
return memento; // handed out as IMemento — opaque to everyone else
}
public void SetMemento(IMemento memento)
{
if (!(memento is Memento)) // guard: reject foreign IMemento implementations
{
return;
}
Console.WriteLine("Restting ID..."); // [sic] typo is in the original
_id = (memento as Memento).Id; // downcast gives the WIDE interface
Console.WriteLine($"ID is reset to {_id}");
}
public void UpdateId()
{
Console.WriteLine("Updating Id...");
_id = Guid.NewGuid();
Console.WriteLine($"Updated ID is now {_id}");
}
}
```
```csharp
// Caretaker.cs — holds history, never inspects it
public class Caretaker
{
// container that could hold memento of Originator object
private readonly Stack<IMemento> _mementos = new();
private readonly IOriginator _originator;
public Caretaker(IOriginator originator)
{
_originator = originator;
}
public void Save()
{
_mementos.Push(_originator.GetMemento());
}
public void Undo()
{
_originator.SetMemento(_mementos.Pop());
}
}
```
`Caretaker` cannot reach the `Guid`. It has no member that returns one, and
`IMemento` declares nothing — the compiler enforces the encapsulation, not a
convention.
```csharp
// Program.cs — the walkthrough (prompts/ReadLine calls elided)
Originator originator = new Originator();
Caretaker caretaker = new Caretaker(originator);
caretaker.Save(); // stack: [id0]
originator.UpdateId(); // → id1 (never saved!)
originator.UpdateId(); // → id2
caretaker.Save(); // stack: [id0, id2]
originator.UpdateId(); // → id3
caretaker.Undo(); // pop id2 → current = id2 (id3 and id1 are gone)
originator.UpdateId(); // → id4
caretaker.Save(); // stack: [id0, id4]
originator.UpdateId(); // → id5
caretaker.Undo(); // pop id4 → current = id4
caretaker.Undo(); // pop id0 → current = id0 (stack now EMPTY)
```
## Output (verified — GUIDs differ every run)
```
Starting ID: cdd24bbf-eac8-40b4-ae9a-9b09db9b2d7c
Creating Memento...
Updating Id...
Updated ID is now e9119540-d546-4c38-bd51-462f90df3e47
Updating Id...
Updated ID is now fcc5d975-3811-4f13-84ab-2768136a83c7
Creating Memento...
Updating Id...
Updated ID is now 02bafbee-f3e7-46d9-a6be-c757f4378595
Restting ID...
ID is reset to fcc5d975-3811-4f13-84ab-2768136a83c7 <- undo skipped 02bafbee
Updating Id...
Updated ID is now ca9f441d-cdf9-4ffa-9269-362291f33235
Creating Memento...
Updating Id...
Updated ID is now b8cfe4d8-de93-4fc9-839d-f63a0a12eb7b
Restting ID...
ID is reset to ca9f441d-cdf9-4ffa-9269-362291f33235
Restting ID...
ID is reset to cdd24bbf-eac8-40b4-ae9a-9b09db9b2d7c <- back to the very first ID
```
The two lessons the run makes concrete: **undo returns to the last state that
was explicitly `Save()`d, not the previous value** (`e9119540` was never saved,
so nothing can ever get back to it); and a stack of mementos gives multi-level
undo for free — the last two `Undo()` calls walk all the way back to the
starting ID.
## GoF pseudocode (text editor, with Command as the caretaker)
```
class Editor is // Originator
private field text, curX, curY, selectionWidth
method createSnapshot(): Snapshot is
// Memento is immutable: state passed once, via the constructor
return new Snapshot(this, text, curX, curY, selectionWidth)
class Snapshot is // Memento
private field editor: Editor
private field text, curX, curY, selectionWidth
method restore() is // memento restores its own originator
editor.setText(text)
editor.setCursor(curX, curY)
editor.setSelectionWidth(selectionWidth)
class Command is // Caretaker
private field backup: Snapshot
method makeBackup() is backup = editor.createSnapshot()
method undo() is if (backup != null) backup.restore()
```
This is the "even stricter encapsulation" variant: the memento links back to its
originator and owns `restore()`, so the caretaker never calls a setter on the
originator at all. The repo's version instead keeps restoration on the
originator (`SetMemento`) — the "intermediate interface" variant.
## Applicability
- You must snapshot (part of) an object's state so you can restore it later —
undo, but equally transaction rollback on error.
- Direct access to that state via getters/setters would expose implementation
details and break encapsulation.
## Pros / Cons
| Pros | Cons |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Snapshots without violating encapsulation | Can consume lots of RAM if snapshots are frequent or large |
| Simplifies the Originator by moving history-keeping to the Caretaker | The caretaker must track the originator's lifecycle to destroy stale mementos |
| Multi-level undo falls out of using a stack | Dynamic languages (PHP/Python/JS) can't truly guarantee the memento stays untouched — C# can |
## Seen in the wild (.NET)
- `TransactionScope` rollback semantics.
- EF Core's change tracker, which holds original values so `Reload()`/rollback
can restore them.
- The undo stack in any editor control.
## Related patterns
- **Command + Memento** is the classic undo pairing: the command captures a
memento immediately before mutating the target, and restores it on undo.
- **Iterator** — a memento can capture iteration state, letting it roll back.
- **Prototype** is a simpler substitute when the object is straightforward and
holds no external resource links.
---
**← Prev:** [Mediator](mediator.md) · [All patterns](README.md) · **Next →**
[Observer](observer.md)

271
docs/observer.md Normal file
View File

@@ -0,0 +1,271 @@
[← Design patterns index](README.md)
# Observer
**Category:** Behavioral | **Also known as: Event-Subscriber, Listener** |
**Source:** `patterns/observer/ObserverPattern/` (+ `UpdatedOne/`)
## Problem
One object's state change must reach an open-ended, changing set of others.
Polling (the customer visiting the store daily) wastes the observers' effort;
broadcasting to everyone regardless of interest (the store emailing all
customers) wastes the publisher's and annoys the uninterested.
## Solution
Give the state-holder (**Subject**/Publisher) a subscription list plus
subscribe/unsubscribe methods. On a state change it walks the list, calling one
common `Update()` method on each subscriber. All subscribers implement a single
interface, so the publisher never knows their concrete types — new subscriber
types can appear with **zero** changes to the publisher.
## Participants (GoF names → this repo)
- **Subject** → `Subject` — the reusable subscription machinery.
- **Observer** → `IObserver` — declares `Update()`.
- **ConcreteSubject** → `Data` — holds the interesting state, calls `Notify()`
on change.
- **ConcreteObserver** → `BarGraph`, `PieChart` — each reacts, and pulls the new
value back from the subject.
```mermaid
classDiagram
class IObserver {
<<interface>>
+Update()
}
class Subject {
-List~IObserver~ Observers
+Subscribe(IObserver)
+Unsubscribe(IObserver)
+Notify()
}
class Data {
-int Value
+SetData(int)
+GetData() int
}
class BarGraph {
-Data _data
+Update()
}
class PieChart {
-Data _data
+Update()
}
Subject <|-- Data
IObserver <|.. BarGraph
IObserver <|.. PieChart
Subject o--> IObserver : notifies - abstract coupling
BarGraph --> Data : pulls value on update
PieChart --> Data : pulls value on update
```
## Code
```csharp
// IObserver.cs — Observer interface
public interface IObserver
{
void Update(); // called when the Subject updates
}
// Subject.cs — reusable subscription machinery, knows nothing about BarGraph or PieChart
public class Subject
{
private readonly List<IObserver> _observers = new();
public void Subscribe(IObserver observer) { _observers?.Add(observer); }
public void Unsubscribe(IObserver observer) { _observers?.Remove(observer); }
public void Notify()
{
foreach (IObserver observer in _observers)
observer.Update(); // abstract coupling: only ever the interface
}
}
```
```csharp
// Data.cs — ConcreteSubject; inherits the subscription machinery
public class Data : Subject
{
private int _value;
public Data(int value) { _value = value; }
public void SetData(int value)
{
_value = value;
Notify(); // every mutation fans out
}
public int GetData() { return _value; }
}
// BarGraph.cs — ConcreteObserver
public class BarGraph : IObserver
{
private readonly Data _data;
public BarGraph(Data data) { _data = data; }
public void Update()
{
Console.WriteLine("Bar Graph:");
Console.WriteLine("\tGot an update notification");
Console.WriteLine("\tUpdating my graph with value " + _data.GetData());
Console.WriteLine();
}
}
// PieChart.cs — ConcreteObserver; same shape as BarGraph, its own message text
public class PieChart : IObserver
{
private readonly Data _data;
public PieChart(Data data) { _data = data; }
public void Update()
{
Console.WriteLine("Pie Chart:");
Console.WriteLine("\tGot an update notification");
Console.WriteLine("\tUpdating my chart with value " + _data.GetData());
Console.WriteLine();
}
}
```
This is the **pull** style: `Update()` takes no parameters, so the observer
calls `_data.GetData()` to fetch what changed. The alternative **push** style
would be `Update(int newValue)`. Pull keeps the interface stable as the
subject's state grows; push avoids the observer needing a back-reference to the
subject at all. GoF: _"The publisher can pass itself as an argument, letting
subscriber fetch any required data directly."_
Subscription itself is explicit, not automatic — neither `BarGraph` nor
`PieChart` calls `Subscribe` on itself. `Program.cs` constructs each observer
and then wires it up separately, so the client controls exactly when an observer
starts listening:
```csharp
// Program.cs — two independent subscribers on one subject
Data data = new Data(2);
BarGraph bar = new BarGraph(data);
PieChart pie = new PieChart(data);
data.Subscribe(bar);
data.Subscribe(pie);
data.SetData(3);
data.SetData(60);
```
Note `Data` never mentions `BarGraph` or `PieChart` by name. A third subscriber
type could be added tomorrow without touching `Data` or `Subject` — that is the
Open/Closed payoff.
## Output (verified)
```
Bar Graph:
Got an update notification
Updating my graph with value 3
Pie Chart:
Got an update notification
Updating my chart with value 3
Bar Graph:
Got an update notification
Updating my graph with value 60
Pie Chart:
Got an update notification
Updating my chart with value 60
```
Both subscribers fire on each `SetData`, and each _pulls_ the current value.
They happen to run in subscription order here because the backing store is a
`List<T>` — but GoF and Shvets both warn that **notification order is not part
of the contract**; never write an observer that depends on running before or
after another.
## GoF pseudocode (typed events via a dedicated event manager)
```
class EventManager is
private field listeners: hash map of event types and listeners
method subscribe(eventType, listener) is listeners.add(eventType, listener)
method unsubscribe(eventType, listener) is listeners.remove(eventType, listener)
method notify(eventType, data) is
foreach (listener in listeners.of(eventType)) do listener.update(data)
class Editor is
private field events: EventManager // composition, not inheritance
constructor Editor() is events = new EventManager()
method openFile(path) is
this.file = new File(path)
events.notify("open", file.name)
method saveFile() is
file.write()
events.notify("save", file.name)
interface EventListener is
method update(filename)
class LoggingListener implements EventListener is
method update(filename) is log.write(...)
class EmailAlertsListener implements EventListener is
method update(filename) is system.email(...)
editor.events.subscribe("open", logger)
editor.events.subscribe("save", emailAlerts)
```
Two upgrades over the repo version worth knowing: **event types**, so
subscribers pick which events they want; and **composition over inheritance** —
`Editor` _has an_ `EventManager` rather than _being a_ `Subject`. The repo uses
inheritance (`Data : Subject`), which burns `Data`'s single base class. Shvets
flags exactly this: put subscription logic in a separate object when the
publisher already has a superclass.
## Applicability
- An abstraction has two aspects, one dependent on the other — separate them to
vary and reuse them independently.
- A change to one object requires changing others, and you don't know how many.
- An object should notify others **without assumptions about who they are**.
- Objects should observe others only for a limited time or in specific cases —
the subscription list is dynamic.
## Pros / Cons
| Pros | Cons |
| ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Open/Closed — new subscribers without touching the publisher | Subscribers are notified in unspecified order |
| Relationships can be made and broken at runtime | **Unexpected updates**: one innocuous change can cascade into a storm of updates |
| Abstract, minimal coupling — subject and observers can live in different layers | Forgetting to unsubscribe leaks the observer (see Pitfalls) |
| Supports broadcast: the sender needn't name a receiver | |
## Seen in the wild (.NET)
- C# `event`/`delegate` — Observer built into the language.
- `IObservable<T>`/`IObserver<T>` (Rx) — the GoF interface pair almost verbatim,
plus the `IDisposable` subscription handle that fixes the unsubscribe problem.
- `INotifyPropertyChanged` powering WPF data binding.
## Related patterns
- One of the four sender/receiver connection strategies alongside **Chain of
Responsibility, Command, Mediator** — Observer's distinguishing trait is
_dynamic_ subscribe/unsubscribe.
- **Mediator vs. Observer** — see the discussion under Mediator; the two are
often combined, with the mediator acting as publisher.
---
**← Prev:** [Memento](memento.md) · [All patterns](README.md) · **Next →**
[State](state.md)

37
docs/principles.md Normal file
View File

@@ -0,0 +1,37 @@
[← Design patterns index](README.md)
# Principles these examples keep demonstrating
These seven patterns all apply the same handful of ideas from _Dive Into Design
Patterns_' design-principles chapter:
- **Encapsulate what varies.** Each pattern isolates the one thing that changes:
the platform (Bridge), the added behavior (Decorator), the subsystem wiring
(Facade), the collaboration rules (Mediator), the saved state (Memento), the
reaction to a change (Observer), or the state-specific behavior (State).
Everything else stays untouched.
- **Program to an interface, not an implementation.** `IMessageSender`,
`IShape`, `IObserver`, `IMemento`, `IOriginator`, and `State` all exist so
that a context or client depends on a contract instead of a concrete type.
`Subject.Notify()` calls `observer.Update()` through `IObserver` alone, which
is exactly why `Data` never needs to know that `PieChart` or `BarGraph`
exists.
- **Favor composition over inheritance.** Bridge, Decorator, Mediator, and State
all answer "my class hierarchy is exploding" the same way: give the object a
_reference_ to another object instead of subclassing again. Shvets: _"with
composition you can easily substitute the linked 'helper' object with another,
changing the behavior of the container at runtime."_
- **Single Responsibility.** _"A class should have just one reason to change."_
Visible in `ShapeDecorator` subclasses (one behavior each), the `State`
subclasses (one state each), and `Facade` (all engine sequencing in one
place).
- **Open/Closed.** _"Classes should be open for extension but closed for
modification."_ The concrete test: adding a new `IObserver`, a new
`ShapeDecorator`, a new `IMessageSender`, or a new `State` requires **zero**
edits to existing classes. Adding a state to `BadCarState` requires editing
four separate `switch` statements — that's the principle failing, made
visible.

359
docs/state.md Normal file
View File

@@ -0,0 +1,359 @@
[← Design patterns index](README.md)
# State
**Category:** Behavioral | **Source:** `patterns/state/BadCarState/` +
`patterns/state/CarState/`
## Problem
Behavior depends on an internal state field, so every state-dependent method
`switch`es on it. As states and behaviors multiply, each method grows a branch
per state, and changing a transition rule means finding and editing that
conditional in _every_ method that has one.
## Solution
Make one class per state, all sharing a common `State` base class. The
**context** holds a reference to its _current_ state object and delegates
state-dependent calls to it instead of branching. Each state class implements
only what makes sense in that state, and states drive transitions by handing the
context a new state object. The object "appears to change its class" because
you're swapping what it delegates to.
## Participants (GoF names → this repo)
- **Context** → `Car` — holds the current `State`, delegates, exposes
`ChangeState`.
- **State** → `State` base class — declares the state-specific operations.
- **ConcreteState** → `Off`, `Stopped`, `Driving`.
```mermaid
stateDiagram-v2
[*] --> Off
Off --> Stopped : turn-clockwise / engine has started
Stopped --> Off : turn-counter / engine has stopped
Stopped --> Driving : gas / car begins to drive
Driving --> Stopped : brake / car slows to a stop
Driving --> Off : turn-counter / engine off, car stops suddenly
Stopped --> Stopped : turn-clockwise / grinding noise
Driving --> Driving : turn-clockwise / grinding noise
```
```mermaid
classDiagram
class Car {
-State _state
+HandleInput(string) bool
+ChangeState(State)
}
class State {
#Car _car
+TurnKeyClockwise() virtual
+TurnKeyCounterClockwise() virtual
+PressGas() virtual
+PressBrake() virtual
}
Car o--> State : delegates
State <|-- Off
State <|-- Stopped
State <|-- Driving
Off ..> Car : calls ChangeState
Stopped ..> Car : calls ChangeState
Driving ..> Car : calls ChangeState
```
## Before — `BadCarState`: a `switch` inside a `switch`
```csharp
public class Car
{
private enum State { Off, Stopped, Driving }
private State _state = State.Off;
public bool HandleInput(string input)
{
switch (input)
{
case "turn-clockwise":
switch (_state) // <-- nested state switch #1
{
case State.Off:
Console.WriteLine("The engine has started");
_state = State.Stopped;
break;
case State.Stopped:
case State.Driving:
Console.WriteLine("You hear a grinding noise...");
break;
}
break;
case "turn-counter":
switch (_state) // <-- nested state switch #2
{
case State.Stopped:
Console.WriteLine("The engine has stopped");
_state = State.Off;
break;
case State.Driving:
Console.WriteLine("The engine turns off, and the car stops suddenly");
_state = State.Off;
break;
}
break;
case "gas":
switch (_state) // <-- nested state switch #3
{
case State.Stopped:
Console.WriteLine("The car begins to drive");
_state = State.Driving;
break;
}
break;
case "brake":
switch (_state) // <-- nested state switch #4
{
case State.Driving:
Console.WriteLine("The car slows to a stop");
_state = State.Stopped;
break;
}
break;
case "exit":
return false;
}
return true;
}
}
```
Adding a `Reversing` state means auditing **all four** nested switches. And
notice there is no `case State.Off` under `"gas"` — pressing the gas while off
silently does nothing, and nothing in the code says whether that was intentional
or an oversight.
## After — `CarState`: one class per state
```csharp
// States/State.cs — base State. Virtual empty bodies = "this input is a no-op here."
public class State
{
protected Car _car; // backreference, so states can transition
public State(Car car) { _car = car; }
public virtual void TurnKeyClockwise() { }
public virtual void TurnKeyCounterClockwise() { }
public virtual void PressGas() { }
public virtual void PressBrake() { }
}
// States/Off.cs — overrides exactly one operation; the rest are inherited no-ops
public class Off : State
{
public Off(Car car) : base(car) { }
public override void TurnKeyClockwise()
{
Console.WriteLine("The engine has started");
_car.ChangeState(new Stopped(_car)); // the state drives its own transition
}
}
// States/Stopped.cs
public class Stopped : State
{
public Stopped(Car car) : base(car) { }
public override void PressGas()
{
Console.WriteLine("The car begins to drive");
_car.ChangeState(new Driving(_car));
}
public override void TurnKeyClockwise()
{
Console.WriteLine("You hear a grinding noise..."); // valid input, no transition
}
public override void TurnKeyCounterClockwise()
{
Console.WriteLine("The engine has stopped");
_car.ChangeState(new Off(_car));
}
}
// States/Driving.cs
public class Driving : State
{
public Driving(Car car) : base(car) { }
public override void TurnKeyClockwise()
{
Console.WriteLine("You hear a grinding noise...");
}
public override void TurnKeyCounterClockwise()
{
Console.WriteLine("The engine turns off, and the car stops suddenly");
_car.ChangeState(new Off(_car));
}
public override void PressBrake()
{
Console.WriteLine("The car slows to a stop");
_car.ChangeState(new Stopped(_car));
}
}
```
```csharp
// Car.cs — the Context. All state branching is gone.
public class Car
{
private State _state;
public Car()
{
ChangeState(new Off(this)); // initial state
}
public bool HandleInput(string input)
{
switch (input) // switches on INPUT only, never on state
{
case "turn-clockwise": _state.TurnKeyClockwise(); break;
case "turn-counter": _state.TurnKeyCounterClockwise(); break;
case "gas": _state.PressGas(); break;
case "brake": _state.PressBrake(); break;
case "exit": return false;
}
return true;
}
public void ChangeState(State state) { _state = state; }
}
```
What actually improved:
- `Car` went from four nested state-switches to four one-line delegations. It no
longer knows the state names at all beyond the initial `Off`.
- Adding `Reversing` = adding **one file**. No existing method changes.
- Each state's complete behavior is readable in one place, instead of scattered
across four switch arms.
- "Gas while off does nothing" is now an explicit, deliberate design statement:
`Off` doesn't override `PressGas`, so the base class's empty body runs.
- **Trade-off:** the transition graph is now distributed across the state
classes rather than visible in one switch. That's why the state diagram shown
earlier is worth keeping next to the code.
## Output (verified — both projects, same scripted input)
Input: `gas`, `turn-clockwise`, `gas`, `turn-clockwise`, `brake`,
`turn-counter`, `exit`
```
Welcome to the Driving Simulater
Usage:
turn-clockwise -Turns the car's key clockwise
turn-counter -Turns the car's key counter clockwise
gas -Presses the gas pedal
brake -Presses the brake pedal
exit -Exits the simulator
> <- "gas" while Off: silently ignored
>
The engine has started
>
The car begins to drive
>
You hear a grinding noise...
>
The car slows to a stop
>
The engine has stopped
>
goodbye
```
`BadCarState` and `CarState` produce **byte-identical output**. That's the
headline: the refactor changed structure and nothing else. The first blank
prompt is the silent no-op discussed earlier, present in both versions.
## GoF pseudocode (audio player)
```
class AudioPlayer is // Context
field state: State
constructor AudioPlayer() is this.state = new ReadyState(this)
method changeState(state: State) is this.state = state
method clickPlay() is state.clickPlay() // pure delegation
method startPlayback() is // service method a state can call back into
abstract class State is
protected field player: AudioPlayer
constructor State(player) is this.player = player
abstract method clickLock()
abstract method clickPlay()
class ReadyState extends State is
method clickPlay() is
player.startPlayback()
player.changeState(new PlayingState(player))
class PlayingState extends State is
method clickPlay() is
player.stopPlayback()
player.changeState(new ReadyState(player))
class LockedState extends State is
method clickPlay() is // Locked, so do nothing.
```
GoF makes `State` **abstract** with abstract methods, forcing every state to
answer every input explicitly. The repo instead uses virtual empty bodies, so
"do nothing" is the default. Abstract = the compiler catches states you forgot
to handle; virtual = less boilerplate. Pick deliberately.
## Applicability
- An object's behavior depends on its state and must change at run-time
accordingly, the number of states is large, and state-specific code changes
often.
- Operations have large multipart conditionals branching on the object's state
(usually enum values), and _several_ operations repeat that same conditional
structure.
- Lots of duplicate code across similar states — factor it into intermediate
abstract state classes.
## Pros / Cons
| Pros | Cons |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Single Responsibility — each state's code in its own class | Overkill for a machine with few states that rarely change |
| Open/Closed — add states without touching existing ones | More classes; the transition graph is no longer visible in one place |
| Kills the bulky conditionals in the context | Allocates a new state object on every transition (usually irrelevant; states can be cached/shared if stateless) |
| Makes transitions explicit instead of stray field assignments | |
## Seen in the wild (.NET)
- `TcpConnection`-style protocol handling — GoF's own example.
- Workflow/order-status engines.
- Game character controllers.
- `HttpClient`'s connection lifecycle.
## Related patterns
- **Bridge / State / Strategy** share the composition-delegation structure and
differ in intent.
- **State vs. Strategy** — State is arguably Strategy where the delegates _know
about each other_ and actively swap themselves in
(`_car.ChangeState(new Stopped(_car))`). Strategy objects are independent and
mutually unaware, and are chosen by the client rather than by each other.
---
**← Prev:** [Observer](observer.md) · [All patterns](README.md)