StructuralC++verifiedVerified
Decorator Pattern in C++
Attaches additional responsibilities to an object dynamically by wrapping it in decorator objects that share the same interface.
How to Implement the Decorator Pattern in C++
1Step 1: Define the Component interface
class DataSource {
public:
virtual ~DataSource() = default;
virtual std::string read() = 0;
virtual void write(const std::string& data) = 0;
};2Step 2: Concrete component
class FileDataSource : public DataSource {
std::string data_;
public:
std::string read() override { return data_; }
void write(const std::string& data) override { data_ = data; }
};3Step 3: Base decorator
class DataSourceDecorator : public DataSource {
protected:
std::unique_ptr<DataSource> wrapped_;
public:
explicit DataSourceDecorator(std::unique_ptr<DataSource> source)
: wrapped_(std::move(source)) {}
std::string read() override { return wrapped_->read(); }
void write(const std::string& data) override { wrapped_->write(data); }
};4Step 4: Concrete decorators
class EncryptionDecorator : public DataSourceDecorator {
public:
using DataSourceDecorator::DataSourceDecorator;
void write(const std::string& data) override {
std::string encrypted = "[encrypted:" + data + "]";
wrapped_->write(encrypted);
}
std::string read() override {
auto data = wrapped_->read();
// Simple "decryption" for demo
return data;
}
};
class CompressionDecorator : public DataSourceDecorator {
public:
using DataSourceDecorator::DataSourceDecorator;
void write(const std::string& data) override {
std::string compressed = "[compressed:" + data + "]";
wrapped_->write(compressed);
}
};
int main() {
// Stack decorators: compression -> encryption -> file
auto source = std::make_unique<FileDataSource>();
auto encrypted = std::make_unique<EncryptionDecorator>(std::move(source));
auto compressed = std::make_unique<CompressionDecorator>(std::move(encrypted));
compressed->write("Hello World");
std::cout << compressed->read() << "\n";
}#include <iostream>
#include <string>
#include <memory>
#include <functional>
#include <format>
#include <chrono>
#include <vector>
#include <map>
// [step] Define the service interface
class IHttpClient {
public:
virtual ~IHttpClient() = default;
struct Response {
int statusCode;
std::string body;
std::chrono::milliseconds latency;
};
virtual Response get(const std::string& url) = 0;
virtual Response post(const std::string& url, const std::string& body) = 0;
};
// [step] Concrete component
class HttpClient : public IHttpClient {
public:
Response get(const std::string& url) override {
return {200, std::format("GET {}", url), std::chrono::milliseconds{50}};
}
Response post(const std::string& url, const std::string& body) override {
return {201, std::format("POST {} body={}", url, body.size()),
std::chrono::milliseconds{100}};
}
};
// [step] Logging decorator
class LoggingHttpClient : public IHttpClient {
std::unique_ptr<IHttpClient> inner_;
public:
explicit LoggingHttpClient(std::unique_ptr<IHttpClient> inner)
: inner_(std::move(inner)) {}
Response get(const std::string& url) override {
std::cout << std::format("[LOG] GET {}\n", url);
auto resp = inner_->get(url);
std::cout << std::format("[LOG] -> {} ({}ms)\n",
resp.statusCode, resp.latency.count());
return resp;
}
Response post(const std::string& url, const std::string& body) override {
std::cout << std::format("[LOG] POST {} ({} bytes)\n", url, body.size());
auto resp = inner_->post(url, body);
std::cout << std::format("[LOG] -> {} ({}ms)\n",
resp.statusCode, resp.latency.count());
return resp;
}
};
// [step] Retry decorator with backoff
class RetryHttpClient : public IHttpClient {
std::unique_ptr<IHttpClient> inner_;
int maxRetries_;
public:
RetryHttpClient(std::unique_ptr<IHttpClient> inner, int maxRetries = 3)
: inner_(std::move(inner)), maxRetries_(maxRetries) {}
Response get(const std::string& url) override {
return retry([&] { return inner_->get(url); });
}
Response post(const std::string& url, const std::string& body) override {
return retry([&] { return inner_->post(url, body); });
}
private:
Response retry(std::function<Response()> fn) {
for (int i = 0; i <= maxRetries_; ++i) {
try {
auto resp = fn();
if (resp.statusCode < 500) return resp;
if (i == maxRetries_) return resp;
std::cout << std::format("[RETRY] Attempt {} failed, retrying...\n", i + 1);
} catch (...) {
if (i == maxRetries_) throw;
}
}
return {500, "Max retries exceeded", std::chrono::milliseconds{0}};
}
};
// [step] Cache decorator
class CachingHttpClient : public IHttpClient {
std::unique_ptr<IHttpClient> inner_;
std::map<std::string, Response> cache_;
public:
explicit CachingHttpClient(std::unique_ptr<IHttpClient> inner)
: inner_(std::move(inner)) {}
Response get(const std::string& url) override {
auto it = cache_.find(url);
if (it != cache_.end()) {
std::cout << std::format("[CACHE] HIT {}\n", url);
return it->second;
}
auto resp = inner_->get(url);
cache_[url] = resp;
return resp;
}
Response post(const std::string& url, const std::string& body) override {
// POST requests are never cached
cache_.erase(url);
return inner_->post(url, body);
}
};
// [step] Stack decorators to build a full-featured client
int main() {
auto client = std::make_unique<HttpClient>();
auto cached = std::make_unique<CachingHttpClient>(std::move(client));
auto retried = std::make_unique<RetryHttpClient>(std::move(cached));
auto logged = std::make_unique<LoggingHttpClient>(std::move(retried));
auto r1 = logged->get("https://api.example.com/users");
std::cout << std::format("Result: {}\n\n", r1.body);
auto r2 = logged->get("https://api.example.com/users"); // cache hit
std::cout << std::format("Result: {}\n\n", r2.body);
auto r3 = logged->post("https://api.example.com/users", R"({"name":"Alice"})");
std::cout << std::format("Result: {}\n", r3.body);
}Decorator Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Decorator Pattern in the Real World
“Think of adding espresso shots and syrups to a coffee order. A plain coffee is the base component. Each addition—an espresso shot, vanilla syrup, oat milk—is a decorator that wraps the previous cup, adding its own cost and flavor. You can combine them in any order without the café needing a separate menu item for every combination.”