9.0 KiB
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
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
// 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"); }
}
// 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();
}
// 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"
// 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/finaland 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.Streamis the canonical case:GZipStream,CryptoStream, andBufferedStreameach wrap anotherStreamand are themselves aStream— exactly the GoF pseudocode shown earlier.- ASP.NET Core middleware.
IMemoryCache/HttpMessageHandlerwrappers.
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 · All patterns · Next → Facade