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

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)