Decorator
The Decorator pattern wraps an object to add behaviour without changing the object’s class. Classic OO uses subclasses; modern languages express it with functions / higher-order wrappers / annotations.
Logging + caching + auth decorators
EXAMPLE
// 1) JavaScript — function decorators
function withLogging(fn, name = fn.name) {
return async function (...args) {
const start = Date.now();
try {
const result = await fn.apply(this, args);
console.log(`[${name}] ok in ${Date.now() - start}ms`);
return result;
} catch (e) {
console.error(`[${name}] fail`, e);
throw e;
}
};
}
function withCache(fn, ttlMs = 60_000) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
const hit = cache.get(key);
if (hit && Date.now() - hit.t < ttlMs) return hit.v;
const v = fn.apply(this, args);
cache.set(key, { v, t: Date.now() });
return v;
};
}
function withAuth(fn, role) {
return function (ctx, ...args) {
if (!ctx.user || (role && ctx.user.role !== role)) {
throw new Error('forbidden');
}
return fn.call(this, ctx, ...args);
};
}
// Compose
const getPosts = withLogging(withCache(fetchPostsFromDb, 30_000), 'getPosts');
const admin = withLogging(withAuth(dangerousAction, 'admin'));
// 2) Python decorators — the same idea, syntactic sugar
from functools import wraps, cache
import time, logging
def with_logging(fn):
@@wraps(fn)
def inner(*args, **kwargs):
t = time.perf_counter()
try:
r = fn(*args, **kwargs)
logging.info(f'{fn.__name__} ok in {(time.perf_counter()-t)*1000:.0f}ms')
return r
except Exception:
logging.exception(f'{fn.__name__} fail')
raise
return inner
@@with_logging
@@cache # functools.cache — memoise
def compute_signature(data):
return hashlib.sha256(data).hexdigest()
# 3) Decorators with arguments
def rate_limit(per_minute):
def deco(fn):
bucket = []
@@wraps(fn)
def inner(*a, **kw):
now = time.time()
bucket[:] = [t for t in bucket if t > now - 60]
if len(bucket) >= per_minute:
raise RateLimited()
bucket.append(now)
return fn(*a, **kw)
return inner
return deco
@@rate_limit(per_minute=30)
def send_otp(phone):
sms.send(phone)
// 4) OO Decorator — classical pattern
// Coffee shop — wrap a Beverage with additions
abstract class Beverage {
abstract cost(): number;
abstract describe(): string;
}
class Espresso extends Beverage {
cost() { return 3.50; }
describe() { return 'espresso'; }
}
abstract class Addon extends Beverage {
constructor(protected base: Beverage) { super(); }
}
class Milk extends Addon {
cost() { return this.base.cost() + 0.50; }
describe() { return `${this.base.describe()} + milk`; }
}
class Caramel extends Addon {
cost() { return this.base.cost() + 0.75; }
describe() { return `${this.base.describe()} + caramel`; }
}
const order = new Caramel(new Milk(new Espresso()));
console.log(order.describe(), order.cost()); // espresso + milk + caramel 4.75
Why it matters
In functional languages, decorators are just higher-order functions — you compose them naturally. In classical OO, the pattern formalises the same idea: composition over inheritance, adding behaviour at runtime without exploding the class hierarchy.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
function withLogging(fn) {
return (...args) => {
console.log('call', fn.name, args);
const r = fn(...args);
console.log('=> ', r);
return r;
};
}
const safeAdd = withLogging(add);
Try it Yourself »
Discussion
Loading…