Files
Design-Patterns/docs/decorator.md
2026-08-13 22:47:17 -04:00

9.0 KiB

← Design patterns index

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/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.
  • 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