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.
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 interfaceclass Stream {public: virtual void write(string data) = 0;};
// ConcreteComponent — the base implementationclass FileStream : public Stream { void write(string data) override { /* write to file */ }};
// Decorator base — holds a Stream* and delegatesclass 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 delegationclass 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 combinationauto s = make_unique<FileStream>();s = make_unique<EncryptDecorator>(move(s));s = make_unique<CompressDecorator>(move(s));s->write("hello"); // compress → encrypt → file writeWhat this lesson walks through
- 01The inheritance explosion problem
- 02ConcreteComponent — the raw implementation
- 03EncryptDecorator wraps FileStream
- 04CompressDecorator wraps EncryptDecorator — stacked decorators
- 05Full call trace through the decorator chain
- 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.