Runtime behavior extension
The Decorator pattern attaches additional responsibilities to an object dynamically. It provides a flexible alternative to subclassing for extending functionality. In Rust, we implement decorators using composition and trait delegation.
Decorators are useful when:
use std::time::{Duration, Instant};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
/// Base trait for a data source
pub trait DataSource {
fn read(&self, key: &str) -> Option<String>;
fn write(&mut self, key: &str, value: &str);
}
/// Concrete implementation: in-memory storage
#[derive(Default)]
pub struct InMemoryDataSource {
data: HashMap<String, String>,
}
impl InMemoryDataSource {
pub fn new() -> Self {
Self::default()
}
}
impl DataSource for InMemoryDataSource {
fn read(&self, key: &str) -> Option<String> {
self.data.get(key).cloned()
}
fn write(&mut self, key: &str, value: &str) {
self.data.insert(key.to_string(), value.to_string());
}
}
/// Decorator: Logging wrapper
pub struct LoggingDecorator<D: DataSource> {
inner: D,
prefix: String,
}
impl<D: DataSource> LoggingDecorator<D> {
pub fn new(inner: D, prefix: &str) -> Self {
LoggingDecorator {
inner,
prefix: prefix.to_string(),
}
}
}
impl<D: DataSource> DataSource for LoggingDecorator<D> {
fn read(&self, key: &str) -> Option<String> {
println!("[{}] Reading key: {}", self.prefix, key);
let result = self.inner.read(key);
println!("[{}] Read result: {:?}", self.prefix, result);
result
}
fn write(&mut self, key: &str, value: &str) {
println!("[{}] Writing key: {} = {}", self.prefix, key, value);
self.inner.write(key, value);
}
}
/// Decorator: Caching layer
pub struct CachingDecorator<D: DataSource> {
inner: D,
cache: HashMap<String, (String, Instant)>,
ttl: Duration,
}
impl<D: DataSource> CachingDecorator<D> {
pub fn new(inner: D, ttl: Duration) -> Self {
CachingDecorator {
inner,
cache: HashMap::new(),
ttl,
}
}
fn is_valid(&self, key: &str) -> bool {
if let Some((_, cached_at)) = self.cache.get(key) {
cached_at.elapsed() < self.ttl
} else {
false
}
}
}
impl<D: DataSource> DataSource for CachingDecorator<D> {
fn read(&self, key: &str) -> Option<String> {
// Check cache first
if self.is_valid(key) {
if let Some((value, _)) = self.cache.get(key) {
println!("[Cache] HIT for key: {}", key);
return Some(value.clone());
}
}
println!("[Cache] MISS for key: {}", key);
self.inner.read(key)
}
fn write(&mut self, key: &str, value: &str) {
// Update cache
self.cache.insert(key.to_string(), (value.to_string(), Instant::now()));
// Write through to inner
self.inner.write(key, value);
}
}
/// Decorator: Encryption layer
pub struct EncryptionDecorator<D: DataSource> {
inner: D,
key: u8, // Simple XOR key for demonstration
}
impl<D: DataSource> EncryptionDecorator<D> {
pub fn new(inner: D, key: u8) -> Self {
EncryptionDecorator { inner, key }
}
fn encrypt(&self, data: &str) -> String {
data.bytes().map(|b| (b ^ self.key) as char).collect()
}
fn decrypt(&self, data: &str) -> String {
// XOR is symmetric
self.encrypt(data)
}
}
impl<D: DataSource> DataSource for EncryptionDecorator<D> {
fn read(&self, key: &str) -> Option<String> {
self.inner.read(key).map(|v| self.decrypt(&v))
}
fn write(&mut self, key: &str, value: &str) {
let encrypted = self.encrypt(value);
self.inner.write(key, &encrypted);
}
}
/// Decorator: Retry wrapper
pub struct RetryDecorator<D: DataSource> {
inner: D,
max_retries: u32,
}
impl<D: DataSource> RetryDecorator<D> {
pub fn new(inner: D, max_retries: u32) -> Self {
RetryDecorator { inner, max_retries }
}
}
impl<D: DataSource> DataSource for RetryDecorator<D> {
fn read(&self, key: &str) -> Option<String> {
for attempt in 0..=self.max_retries {
if let Some(value) = self.inner.read(key) {
return Some(value);
}
if attempt < self.max_retries {
println!("[Retry] Attempt {} failed, retrying...", attempt + 1);
}
}
None
}
fn write(&mut self, key: &str, value: &str) {
self.inner.write(key, value);
}
}
/// Function decorator using closures
pub fn with_timing<F, R>(name: &str, f: F) -> R
where
F: FnOnce() -> R,
{
let start = Instant::now();
let result = f();
println!("[{}] Completed in {:?}", name, start.elapsed());
result
}
/// Trait-based decorator for HTTP handlers
pub trait HttpHandler {
fn handle(&self, request: &HttpRequest) -> HttpResponse;
}
#[derive(Debug)]
pub struct HttpRequest {
pub path: String,
pub auth_token: Option<String>,
}
#[derive(Debug)]
pub struct HttpResponse {
pub status: u16,
pub body: String,
}
/// Concrete handler
pub struct ApiHandler;
impl HttpHandler for ApiHandler {
fn handle(&self, request: &HttpRequest) -> HttpResponse {
HttpResponse {
status: 200,
body: format!("Handled: {}", request.path),
}
}
}
/// Authentication decorator
pub struct AuthDecorator<H: HttpHandler> {
inner: H,
valid_token: String,
}
impl<H: HttpHandler> AuthDecorator<H> {
pub fn new(inner: H, valid_token: &str) -> Self {
AuthDecorator {
inner,
valid_token: valid_token.to_string(),
}
}
}
impl<H: HttpHandler> HttpHandler for AuthDecorator<H> {
fn handle(&self, request: &HttpRequest) -> HttpResponse {
match &request.auth_token {
Some(token) if token == &self.valid_token => {
self.inner.handle(request)
}
_ => HttpResponse {
status: 401,
body: "Unauthorized".to_string(),
},
}
}
}
/// Timing decorator for HTTP
pub struct TimingDecorator<H: HttpHandler> {
inner: H,
}
impl<H: HttpHandler> TimingDecorator<H> {
pub fn new(inner: H) -> Self {
TimingDecorator { inner }
}
}
impl<H: HttpHandler> HttpHandler for TimingDecorator<H> {
fn handle(&self, request: &HttpRequest) -> HttpResponse {
let start = Instant::now();
let response = self.inner.handle(request);
println!("Request to {} took {:?}", request.path, start.elapsed());
response
}
}
/// Builder for composing decorators
pub struct DataSourceBuilder {
source: Box<dyn DataSource>,
}
impl DataSourceBuilder {
pub fn new() -> Self {
DataSourceBuilder {
source: Box::new(InMemoryDataSource::new()),
}
}
pub fn with_logging(self, prefix: &str) -> Self {
// Note: This is simplified; real implementation would need different approach
// due to Box<dyn> not being directly wrappable
self
}
pub fn build(self) -> Box<dyn DataSource> {
self.source
}
}
fn main() {
// Basic decorator chain
let storage = InMemoryDataSource::new();
let mut logged = LoggingDecorator::new(storage, "DB");
logged.write("user:1", "Alice");
let _ = logged.read("user:1");
println!("\n=== Stacked Decorators ===");
// Stack multiple decorators
let storage = InMemoryDataSource::new();
let encrypted = EncryptionDecorator::new(storage, 42);
let cached = CachingDecorator::new(encrypted, Duration::from_secs(60));
let mut logged = LoggingDecorator::new(cached, "APP");
logged.write("secret", "password123");
let _ = logged.read("secret"); // Cache miss
let _ = logged.read("secret"); // Cache hit
println!("\n=== HTTP Handler Decorators ===");
// HTTP handler with decorators
let handler = ApiHandler;
let authed = AuthDecorator::new(handler, "secret-token");
let timed = TimingDecorator::new(authed);
// Authorized request
let request = HttpRequest {
path: "/api/users".to_string(),
auth_token: Some("secret-token".to_string()),
};
let response = timed.handle(&request);
println!("Response: {:?}", response);
// Unauthorized request
let bad_request = HttpRequest {
path: "/api/users".to_string(),
auth_token: None,
};
let response = timed.handle(&bad_request);
println!("Response: {:?}", response);
println!("\n=== Function Decorator ===");
let result = with_timing("compute", || {
std::thread::sleep(Duration::from_millis(100));
42
});
println!("Result: {}", result);
}
| Pattern | Implementation | Use Case |
|---------|----------------|----------|
| Struct Wrapper | struct Dec | Most common |
| Closure Wrapper | fn wrap | Functions |
| Blanket Impl | impl | Universal decoration |
| Newtype | struct Dec(Inner) | Simple transformations |
// DON'T: Decorator that changes semantics
impl<D: DataSource> DataSource for BadDecorator<D> {
fn read(&self, key: &str) -> Option<String> {
// Silently returns different data!
Some("fake data".to_string())
}
}
// DON'T: Deep nesting without type aliases
let d = LoggingDecorator::new(
CachingDecorator::new(
EncryptionDecorator::new(
RetryDecorator::new(
InMemoryDataSource::new(),
3
),
42
),
Duration::from_secs(60)
),
"DB"
);
// DO: Use type aliases or builders
type CachedEncryptedSource = CachingDecorator<EncryptionDecorator<InMemoryDataSource>>;
BufReader, BufWriter decoratorsRun this code in the official Rust Playground