Table of contents
Open Table of contents
Problem Statement
Build a coffee ordering system using the decorator pattern:
- Define a
Coffeeinterface withcost()anddescription()methods - Create a
SimpleCoffeebase implementation - Create decorators:
MilkDecorator,SugarDecorator,WhipCreamDecorator - 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
@decoratorsyntax is the most natural fit for this pattern. Thefunctools.wrapsdecorator 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
@loggedsyntax 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"))).