# Design Patterns Exercises

## Exercise 1: Strategy — Sorting

**Task:** Implement a `Sorter` that uses a pluggable sort strategy. Provide `BubbleSort` and `QuickSort` strategies. The Sorter should have `sort(arr)` and `setStrategy(s)`.

**Validation:**
- [ ] Sorter delegates to strategy
- [ ] Can switch strategies at runtime
- [ ] Both strategies produce correct output

**Hints:**
1. interface SortStrategy { sort(arr: number[]): number[] }
2. Sorter holds strategy, calls strategy.sort(arr)
3. setStrategy replaces the current strategy

---

## Exercise 2: Factory — Shape Creation

**Task:** Create a `ShapeFactory.create(type: 'circle' | 'square' | 'triangle')` that returns the appropriate shape object. Each shape has `area()` and `perimeter()`.

**Validation:**
- [ ] Factory returns correct type for each input
- [ ] Invalid type throws or returns null
- [ ] Adding a new shape requires only a new case, not changes to callers

**Hints:**
1. interface Shape { area(): number; perimeter(): number }
2. switch (type) { case 'circle': return new Circle(...) }
3. Consider default for unknown type

---

## Exercise 3: Observer — Event Bus

**Task:** Build an `EventBus` with `on(event, handler)`, `off(event, handler)`, and `emit(event, data)`. Support multiple handlers per event. Test with at least 2 events and 2 handlers on one event.

**Validation:**
- [ ] Handlers are called on emit
- [ ] off removes the correct handler
- [ ] Multiple handlers work
- [ ] Same handler can be on multiple events

**Hints:**
1. Map<event, Set<handler>>
2. emit: get handlers, call each with data
3. off: remove from Set

---

## Exercise 4: Decorator — Caching

**Task:** Create a `CachedFunction` decorator that wraps any async function `(x) => Promise<y>` and caches results by input. Same input returns cached value without calling the inner function.

**Validation:**
- [ ] First call invokes inner function
- [ ] Second call with same input returns cache
- [ ] Different inputs call inner function
- [ ] Decorator is reusable for any async function

**Hints:**
1. type Fn = (x: T) => Promise<U>
2. const cache = new Map<T, U>()
3. if (cache.has(x)) return cache.get(x)!; const r = await fn(x); cache.set(x, r); return r

---

## Exercise 5: Adapter — Old API to New

**Task:** You have `LegacyStorage.getItem(key, callback)` and `LegacyStorage.setItem(key, value, callback)`. Create an adapter with `async get(key): Promise<string | null>` and `async set(key, value): Promise<void>`.

**Validation:**
- [ ] Adapter wraps LegacyStorage
- [ ] get/set return Promises
- [ ] Callbacks are correctly translated to Promise resolve
- [ ] Errors in callback reject the Promise

**Hints:**
1. new Promise((resolve, reject) => legacy.getItem(key, (err, val) => err ? reject(err) : resolve(val)))
2. Same pattern for setItem
3. Wrap both in a class or object
