Add code examples, and documentation
This commit is contained in:
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)
|
||||
Reference in New Issue
Block a user