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

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)