# Decorator, Function Decorators and Middleware — Design Patterns

Source: https://www.skillbyai.com/en/design-patterns/s-decorator

> Add behaviour by wrapping.

## Same interface, extra behaviour

**Intent:** attach additional responsibilities to an object dynamically by wrapping it in another object with the **same interface**. Decorators can be stacked: retries around logging around the real HTTP client. This avoids a subclass explosion (`LoggingRetryingCachingClient`). The same idea appears in functional form as **higher-order functions** (a function that takes a function and returns an enhanced one), in Python's `@decorator` syntax, in TypeScript decorators (check your TypeScript version and configuration, since the standard and legacy "experimental" decorators differ), and in **middleware** stacks in web frameworks. **Use it for** cross-cutting concerns: logging, caching, retries, metrics, authorisation. **Watch out** for deep stacks that make stack traces and ordering hard to follow.

## A retry decorator as a higher-order function

Wraps any async function with the same signature.

```typescript
type AsyncFn<A extends unknown[], R> = (...args: A) => Promise<R>;

function withRetry<A extends unknown[], R>(fn: AsyncFn<A, R>, attempts = 3, delayMs = 200): AsyncFn<A, R> {
  return async (...args: A) => {
    let lastError: unknown;
    for (let i = 0; i < attempts; i++) {
      try {
        return await fn(...args);
      } catch (err) {
        lastError = err;
        await new Promise((r) => setTimeout(r, delayMs * 2 ** i));
      }
    }
    throw lastError;
  };
}

function withTiming<A extends unknown[], R>(name: string, fn: AsyncFn<A, R>): AsyncFn<A, R> {
  return async (...args: A) => {
    const start = performance.now();
    try { return await fn(...args); }
    finally { metrics.record(name, performance.now() - start); }
  };
}

// Stack decorators: timing wraps retries wraps the real call
const fetchExchangeRate = withTiming('fx.fetch', withRetry(rawFetchExchangeRate));
```

## Order matters

Timing outside retries measures total time including retries; timing inside measures each attempt. Decide deliberately, and document the order of middleware.

**Quiz:** What distinguishes a Decorator from an Adapter?

- [ ] They are identical
- [ ] A Decorator always uses inheritance
- [ ] An Adapter adds caching
- [x] A Decorator keeps the same interface and adds behaviour; an Adapter changes the interface

*Answer:* A Decorator keeps the same interface and adds behaviour; an Adapter changes the interface. Decorators enhance; adapters translate.
