design patterns · medium

GoF Decorator — Runtime Behaviour Composition Without Subclassing

The Decorator (Wrapper) pattern attaches additional behaviours to objects at runtime via composition. A ConcreteComponent (FileStream) and Decorator (StreamDecorator) both implement the same Component interface. The Decorator holds a Component* (wrapped_) and delegates calls to it after adding its own pre/post logic. Concrete Decorators (EncryptDecorator, CompressDecorator) implement specific additions. They chain: client → CompressDecorator → EncryptDecorator → FileStream. Compared to inheritance, Decorator avoids 2^N class explosion for N features. Key insight: Decorator is both IS-A Component (so it's transparent) and HAS-A Component (so it composes). Order of wrapping matters. Real examples: std::ostream buffering, HTTP middleware chains, Qt style sheets.

🔑 Key line

Decorator: ConcreteComponent + Decorator both implement Component interface; Decorator holds Component* and delegates; wrapping any number adds behaviour without modifying existing classes — N features = N classes, any combination composable at runtime.

The code

// Component interface
class Stream {
public:
virtual void write(string data) = 0;
};
// ConcreteComponent — the base implementation
class FileStream : public Stream {
void write(string data) override { /* write to file */ }
};
// Decorator base — holds a Stream* and delegates
class StreamDecorator : public Stream {
unique_ptr<Stream> wrapped_;
public:
StreamDecorator(unique_ptr<Stream> s) : wrapped_(move(s)) {}
void write(string data) override {
wrapped_->write(data);
}
};
// Concrete Decorators — add behaviour before/after delegation
class EncryptDecorator : public StreamDecorator {
void write(string data) override {
auto enc = encrypt(data); // before
StreamDecorator::write(enc); // delegate
}
};
class CompressDecorator : public StreamDecorator {
void write(string data) override {
auto comp = compress(data); // before
StreamDecorator::write(comp); // delegate
}
};
// Compose at runtime — any combination
auto s = make_unique<FileStream>();
s = make_unique<EncryptDecorator>(move(s));
s = make_unique<CompressDecorator>(move(s));
s->write("hello"); // compress → encrypt → file write

What this lesson walks through

  1. 01The inheritance explosion problem
  2. 02ConcreteComponent — the raw implementation
  3. 03EncryptDecorator wraps FileStream
  4. 04CompressDecorator wraps EncryptDecorator — stacked decorators
  5. 05Full call trace through the decorator chain
  6. 06Decorator in real C++ — std::ostream, Qt, middleware

If you use inheritance to add behaviours (EncryptedStream, CompressedStream, EncryptedCompressedStream...) you get 2^N classes for N features. Adding a BufferedStream means N more subclasses. Decorator solves this: N features = N decorator classes, compose any combination at runtime.

See it animated — step by step, at your own pace

Unlock the full interactive walkthrough of GoF Decorator — Runtime Behaviour Composition Without Subclassing and 100+ animated C++ interview lessons.

← Previous
Prototype Pattern
Next →
Adapter Pattern