Decorator Pattern
Decorator Pattern
Section titled “Decorator Pattern”The Decorator pattern attaches additional behaviour to an object at runtime by wrapping it in decorator objects. It’s an alternative to subclassing for extending functionality.
When to Use
Section titled “When to Use”- Adding cross-cutting concerns (logging, caching, validation, auth) without modifying the core class
- When you need combinations of behaviour that would create an explosion of subclasses
- Middleware pipelines (HTTP middleware, command pipelines)
C# Implementation
Section titled “C# Implementation”// Core interfacepublic interface IProductRepository{ Task<Product?> GetByIdAsync(int id); Task<IEnumerable<Product>> GetAllAsync(); Task SaveAsync(Product product);}
// Real implementationpublic class ProductRepository : IProductRepository{ private readonly AppDbContext _db; public ProductRepository(AppDbContext db) => _db = db;
public async Task<Product?> GetByIdAsync(int id) => await _db.Products.FindAsync(id);
public async Task<IEnumerable<Product>> GetAllAsync() => await _db.Products.ToListAsync();
public async Task SaveAsync(Product product) { _db.Products.Update(product); await _db.SaveChangesAsync(); }}
// Caching decoratorpublic class CachedProductRepository : IProductRepository{ private readonly IProductRepository _inner; private readonly IMemoryCache _cache;
public CachedProductRepository(IProductRepository inner, IMemoryCache cache) { _inner = inner; _cache = cache; }
public async Task<Product?> GetByIdAsync(int id) { var key = $"product:{id}"; if (_cache.TryGetValue(key, out Product? cached)) return cached;
var product = await _inner.GetByIdAsync(id); if (product != null) _cache.Set(key, product, TimeSpan.FromMinutes(5));
return product; }
public Task<IEnumerable<Product>> GetAllAsync() => _inner.GetAllAsync();
public async Task SaveAsync(Product product) { await _inner.SaveAsync(product); _cache.Remove($"product:{product.Id}"); }}
// Logging decoratorpublic class LoggingProductRepository : IProductRepository{ private readonly IProductRepository _inner; private readonly ILogger<LoggingProductRepository> _logger;
public LoggingProductRepository(IProductRepository inner, ILogger<LoggingProductRepository> logger) { _inner = inner; _logger = logger; }
public async Task<Product?> GetByIdAsync(int id) { _logger.LogInformation("Fetching product {Id}", id); var result = await _inner.GetByIdAsync(id); _logger.LogInformation("Product {Id} {Found}", id, result != null ? "found" : "not found"); return result; }
public Task<IEnumerable<Product>> GetAllAsync() => _inner.GetAllAsync(); public Task SaveAsync(Product product) => _inner.SaveAsync(product);}
// Compose decorators in DIservices.AddScoped<IProductRepository>(provider =>{ var db = provider.GetRequiredService<AppDbContext>(); var cache = provider.GetRequiredService<IMemoryCache>(); var logger = provider.GetRequiredService<ILogger<LoggingProductRepository>>();
IProductRepository repo = new ProductRepository(db); repo = new CachedProductRepository(repo, cache); repo = new LoggingProductRepository(repo, logger); return repo;});TypeScript — Function Decorator
Section titled “TypeScript — Function Decorator”// Higher-order function as decoratorfunction withLogging<T extends (...args: any[]) => any>(fn: T, name: string): T { return ((...args: Parameters<T>): ReturnType<T> => { console.log(`${name} called with`, args); const result = fn(...args); console.log(`${name} returned`, result); return result; }) as T;}
function add(a: number, b: number): number { return a + b;}
const loggedAdd = withLogging(add, 'add');loggedAdd(2, 3); // logs: "add called with [2, 3]", "add returned 5"Middleware as Decorator
Section titled “Middleware as Decorator”Express middleware is the Decorator pattern applied to HTTP handlers:
// Each middleware wraps the next oneapp.use(cors()); // decorates with CORS headersapp.use(helmet()); // decorates with security headersapp.use(rateLimiter()); // decorates with rate limitingapp.use(authenticate); // decorates with auth checkapp.use(router); // the core handlerPython Decorators
Section titled “Python Decorators”Python has first-class syntax for decorators:
import functoolsimport time
def timed(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) elapsed = time.perf_counter() - start print(f"{func.__name__} took {elapsed:.4f}s") return result return wrapper
@timeddef process_report(data): # ... expensive operation ... return resultComparison: Decorator vs Inheritance
Section titled “Comparison: Decorator vs Inheritance”| Decorator | Inheritance | |
|---|---|---|
| Composition | Runtime | Compile-time |
| Combinations | Mix and match freely | Combinatorial explosion |
| Single Responsibility | Each decorator does one thing | Subclasses accumulate concerns |
| Open/Closed | Add new decorators without modifying existing | Changing base class affects all children |