Skip to content
Code Kata
Go back

Kata: Decorator Pattern

Suggest edit

Table of contents

Open Table of contents

Problem Statement

Build a coffee ordering system using the decorator pattern:

  1. Define a Coffee interface with cost() and description() methods
  2. Create a SimpleCoffee base implementation
  3. Create decorators: MilkDecorator, SugarDecorator, WhipCreamDecorator
  4. Decorators should be stackable: new MilkDecorator(new SugarDecorator(new SimpleCoffee()))

Bonus: Implement a function decorator that adds logging/timing to any function.

Concepts

The decorator pattern wraps an object to extend its behavior without modifying its class. Each decorator implements the same interface as the object it wraps, enabling transparent stacking.

Key distinction from inheritance: Decorators compose behavior at runtime, while inheritance is fixed at compile time.

Python: Python has native decorator syntax (@decorator) which is a different mechanism but related concept. See Python Core Concepts.

TypeScript: Stage 3 decorators (TS 5.0+) provide native class/method decorators. See TypeScript 5.x Updates.

PHP: Attributes (#[...]) serve a similar metadata role. See PHP 8.x Updates.

Boilerplate

TypeScript

// decorator.ts
interface Coffee {
  cost(): number;
  description(): string;
}

class SimpleCoffee implements Coffee {
  cost(): number { /* TODO */ return 0; }
  description(): string { /* TODO */ return ""; }
}

class MilkDecorator implements Coffee {
  constructor(private coffee: Coffee) {}
  cost(): number { /* TODO */ return 0; }
  description(): string { /* TODO */ return ""; }
}

// TODO: SugarDecorator, WhipCreamDecorator

Hints

Hint 1: Decorator structure

Each decorator holds a reference to the wrapped Coffee. Its cost() returns the wrapped coffee’s cost plus its own addition. Same for description().

Hint 2: Function decorator

A function decorator is a higher-order function that takes a function and returns a new function with added behavior (logging, timing, etc.) before/after the original call.

Solution

TypeScript

interface Coffee {
  cost(): number;
  description(): string;
}

class SimpleCoffee implements Coffee {
  cost(): number {
    return 1.0;
  }

  description(): string {
    return "Simple coffee";
  }
}

class MilkDecorator implements Coffee {
  constructor(private coffee: Coffee) {}

  cost(): number {
    return this.coffee.cost() + 0.5;
  }

  description(): string {
    return `${this.coffee.description()}, milk`;
  }
}

class SugarDecorator implements Coffee {
  constructor(private coffee: Coffee) {}

  cost(): number {
    return this.coffee.cost() + 0.25;
  }

  description(): string {
    return `${this.coffee.description()}, sugar`;
  }
}

class WhipCreamDecorator implements Coffee {
  constructor(private coffee: Coffee) {}

  cost(): number {
    return this.coffee.cost() + 0.75;
  }

  description(): string {
    return `${this.coffee.description()}, whip cream`;
  }
}

// Usage
const order = new WhipCreamDecorator(
  new MilkDecorator(new SimpleCoffee())
);

order.description(); // "Simple coffee, milk, whip cream"
order.cost(); // 2.25

// Bonus: Function decorator
function withLogging<T extends (...args: any[]) => any>(fn: T): T {
  return function (this: any, ...args: Parameters<T>): ReturnType<T> {
    const name = fn.name || "anonymous";
    const result = fn.apply(this, args);
    return result;
  } as T;
}

function withTiming<T extends (...args: any[]) => any>(fn: T): T {
  return function (this: any, ...args: Parameters<T>): ReturnType<T> {
    const start = performance.now();
    const result = fn.apply(this, args);
    const elapsed = performance.now() - start;
    return result;
  } as T;
}

Python

from functools import wraps
from typing import Protocol
import time

class Coffee(Protocol):
    def cost(self) -> float: ...
    def description(self) -> str: ...

class SimpleCoffee:
    def cost(self) -> float:
        return 1.0

    def description(self) -> str:
        return "Simple coffee"

class MilkDecorator:
    def __init__(self, coffee: Coffee) -> None:
        self._coffee = coffee

    def cost(self) -> float:
        return self._coffee.cost() + 0.5

    def description(self) -> str:
        return f"{self._coffee.description()}, milk"

class SugarDecorator:
    def __init__(self, coffee: Coffee) -> None:
        self._coffee = coffee

    def cost(self) -> float:
        return self._coffee.cost() + 0.25

    def description(self) -> str:
        return f"{self._coffee.description()}, sugar"

# Python native function decorator
def timer(func):
    @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

@timer
def expensive_computation(n: int) -> int:
    return sum(i * i for i in range(n))

Complexity Analysis

The decorator pattern adds no algorithmic complexity — each decorator is O(1) overhead per call, and they compose linearly. A chain of k decorators adds O(k) to each method call.

Cross-Language Notes

Python: Python’s @decorator syntax is the most natural fit for this pattern. The functools.wraps decorator preserves the original function’s metadata. Class decorators and __init_subclass__ extend this to class-level decoration. See Python Core Concepts.

TypeScript: TS 5.0 Stage 3 decorators provide @logged syntax for class methods, but the structural pattern shown above works for any object composition. See TypeScript 5.x Updates.

PHP: PHP uses composition for the structural decorator pattern. Attributes (#[]) serve a different role — they attach metadata rather than wrapping behavior. Closures can be used for function-level decoration.

Java: Java’s I/O streams are the classic decorator example: new BufferedReader(new InputStreamReader(new FileInputStream("file.txt"))).


Suggest edit
Share this post on:

Previous Post
Kata: Factory Pattern
Next Post
Kata: Strategy Pattern