Add code examples, and documentation
This commit is contained in:
84
docs/README.md
Normal file
84
docs/README.md
Normal 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
205
docs/bridge.md
Normal 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)
|
||||
20
docs/cross-pattern-comparisons.md
Normal file
20
docs/cross-pattern-comparisons.md
Normal 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
253
docs/decorator.md
Normal 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
177
docs/facade.md
Normal 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
284
docs/mediator.md
Normal 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
275
docs/memento.md
Normal 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
271
docs/observer.md
Normal 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
37
docs/principles.md
Normal 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
359
docs/state.md
Normal 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)
|
||||
Reference in New Issue
Block a user