← All Patterns
structural4 participants

Decorator

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

The Problem

Inheritance for extending behavior leads to a class explosion: BufferedEncryptedCompressedStream, UnbufferedEncryptedStream, BufferedStream... every combination needs its own class. You can't add buffering to an existing object at runtime — you'd have to create a new instance.

Structure

FileStreamread(buf,n)«raw I/O»BufferedStreamread(buf,n)«+buffering»CryptoStreamread(buf,n)«+encryption»inner_→read()ClientStream* s =Crypto( Buffered( File(fd)))s→read(...);← each layer wraps inner_ →Same Stream interface — behavior added transparently at each layer

Execution Walkthrough

1

Component interface

Stream interface defines read()/write(). All concrete streams and decorators implement this exact interface.

2

Base component

3

Decorator base

4

Concrete decorators

5

Compose at runtime

Code Comparison

Every combination of features requires its own class. 3 features × combinations = 7 classes minimum.

// BAD: class explosion with inheritance
class Stream              { virtual size_t read(char*, size_t) = 0; };
class FileStream          : public Stream { /* raw I/O */ };
class BufferedStream      : public Stream { /* buffered I/O */ };
class CryptoStream        : public Stream { /* encrypted I/O */ };
class BufferedCrypto      : public Stream { /* buffered + encrypted */ };
class CompressedStream    : public Stream { /* compressed I/O */ };
class BufferedCompressed  : public Stream { /* buffered + compressed */ };
class AllThree            : public Stream { /* all three */ };
// Add a 4th feature? Double the subclasses again.

Full C++ Implementation

C++ · Decorator
#include <memory>
#include <cstring>
#include <algorithm>
#include <unistd.h>

struct Stream {
  virtual size_t read(char* buf, size_t n) = 0;
  virtual ~Stream() = default;
};

struct FileStream : Stream {
  int fd_;
  explicit FileStream(int fd) : fd_(fd) {}
  size_t read(char* buf, size_t n) override {
    ssize_t r = ::read(fd_, buf, n);
    return r < 0 ? 0 : static_cast<size_t>(r);
  }
};

struct StreamDecorator : Stream {
  std::unique_ptr<Stream> inner_;
  explicit StreamDecorator(std::unique_ptr<Stream> s)
    : inner_(std::move(s)) {}
  size_t read(char* buf, size_t n) override {
    return inner_->read(buf, n);
  }
};

struct BufferedStream : StreamDecorator {
  char   buf_[4096];
  size_t avail_ = 0, pos_ = 0;
  using StreamDecorator::StreamDecorator;
  size_t read(char* out, size_t n) override {
    if (avail_ == 0) { avail_ = inner_->read(buf_, 4096); pos_ = 0; }
    size_t take = std::min(n, avail_);
    std::memcpy(out, buf_ + pos_, take);
    pos_ += take; avail_ -= take;
    return take;
  }
};

struct CryptoStream : StreamDecorator {
  using StreamDecorator::StreamDecorator;
  size_t read(char* out, size_t n) override {
    size_t r = inner_->read(out, n);
    for (size_t i = 0; i < r; ++i) out[i] ^= 0xAB;
    return r;
  }
};

// Usage: crypto( buffered( file ) )
// auto s = std::make_unique<CryptoStream>(
//            std::make_unique<BufferedStream>(
//             std::make_unique<FileStream>(fd)));

Participants

ComponentConcreteComponentDecoratorConcreteDecorator

Where it is used

C++ I/O streams

std::ifstream wrapped by boost::iostreams filtering — buffered, compressed, encrypted layers stacked.

Middleware stacks

FastAPI/Express: auth → rate-limit → logging → handler. Each middleware wraps the next.

Python decorators

@lru_cache, @staticmethod — each wraps a function and adds caching or binding.

Network sockets

Raw socket → TLS → buffering → compression. Same interface, composed at connection time.

VisitorAdapter