iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…