Examples
Design pattern worked examples: Observer, Strategy, Factory, Adapter, Decorator.
Design patterns — examples
EXAMPLE
// ===== 1. Observer (pub/sub) =====
class EventBus {
private listeners = new Map<string, Function[]>();
on(event: string, fn: Function) {
if (!this.listeners.has(event)) this.listeners.set(event, []);
this.listeners.get(event)!.push(fn);
return () => this.off(event, fn); // unsubscribe
}
off(event: string, fn: Function) {
const arr = this.listeners.get(event);
if (arr) this.listeners.set(event, arr.filter(f => f !== fn));
}
emit(event: string, payload: any) {
for (const fn of this.listeners.get(event) ?? []) fn(payload);
}
}
const bus = new EventBus();
const off = bus.on('order.placed', (o) => console.log('email:', o.id));
bus.emit('order.placed', { id: 1 });
off();
// ===== 2. Strategy =====
interface Pricer { price(order: Order): number }
class RetailPricer implements Pricer { price(o: Order) { return o.subtotal; } }
class WholesalePricer implements Pricer { price(o: Order) { return o.subtotal * 0.9; } }
class VipPricer implements Pricer { price(o: Order) { return o.subtotal * 0.8; } }
function quote(order: Order, pricer: Pricer) {
return pricer.price(order);
}
// ===== 3. Factory =====
interface Animal { speak(): string }
class Dog implements Animal { speak() { return 'woof'; } }
class Cat implements Animal { speak() { return 'meow'; } }
class AnimalFactory {
static create(kind: 'dog' | 'cat'): Animal {
switch (kind) {
case 'dog': return new Dog();
case 'cat': return new Cat();
default: throw new Error('unknown');
}
}
}
const pet = AnimalFactory.create('dog');
// ===== 4. Adapter =====
// Old API:
class LegacyLogger {
log_message(level: string, text: string) { console.log(\`[${level}] ${text}\`); }
}
// New API your code expects:
interface Logger { info(msg: string): void; error(msg: string): void; }
class LegacyLoggerAdapter implements Logger {
constructor(private legacy: LegacyLogger) {}
info(msg: string) { this.legacy.log_message('INFO', msg); }
error(msg: string) { this.legacy.log_message('ERROR', msg); }
}
const logger: Logger = new LegacyLoggerAdapter(new LegacyLogger());
logger.info('hello');
// ===== 5. Decorator =====
interface Coffee { cost(): number }
class SimpleCoffee implements Coffee { cost() { return 5; } }
class MilkDecorator implements Coffee {
constructor(private inner: Coffee) {}
cost() { return this.inner.cost() + 1; }
}
class SugarDecorator implements Coffee {
constructor(private inner: Coffee) {}
cost() { return this.inner.cost() + 0.5; }
}
const order: Coffee = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
console.log(order.cost()); // 6.5
// ===== 6. Singleton (carefully) =====
class Settings {
private static instance: Settings;
private constructor(public data: Record<string, any> = {}) {}
static get(): Settings {
if (!Settings.instance) Settings.instance = new Settings();
return Settings.instance;
}
}
// Use sparingly; singletons hide dependencies.
// ===== Patterns =====
// - Observer for cross-component events
// - Strategy for interchangeable algorithms
// - Factory for hidden construction logic
// - Adapter to bridge incompatible interfaces
// - Decorator for stacking behaviour
// - Singleton only when truly global
// ===== Pitfalls =====
// - Over-applying patterns -> ceremony
// - Strategy with one impl -> just inline
// - Adapter that hides bad APIs -> rewrite if you can
// - Decorators stacked deep -> hard to debug
// - Singletons as hidden global state
Why it matters
Worked design patterns: Observer (pub/sub), Strategy (interchangeable algorithms), Factory (hidden construction), Adapter (bridge APIs), Decorator (stack behaviour), Singleton (use sparingly). Master the shapes; resist applying them where simpler code would do.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Each lesson body shows the SHAPE of the pattern with one runnable snippet.Try it Yourself »
Discussion
Loading…