254 lines
9.0 KiB
Markdown
254 lines
9.0 KiB
Markdown
[← 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)
|