Composition and OOP Patterns
Combining functions and objects for flexible code
Challenge
Section titled “Challenge”Choose between inheritance and composition, combine behaviors flexibly, and avoid brittle hierarchies.
Explanation
Section titled “Explanation”Composition is often superior to inheritance for flexibility and maintainability.
Composition vs Inheritance
Section titled “Composition vs Inheritance”// ❌ Inheritance: Brittle hierarchyclass Animal { move() { return 'moving'; }}
class Bird extends Animal { fly() { return 'flying'; }}
class Fish extends Animal { swim() { return 'swimming'; }}
// Penguin both swims and flies!// How do we express this with inheritance?
// ✅ Composition: Flexible combinationsconst canEat = { eat() { return 'eating'; }};
const canMove = { move() { return 'moving'; }};
const canFly = { fly() { return 'flying'; }};
const canSwim = { swim() { return 'swimming'; }};
function createDuck() { return { ...canEat, ...canMove, ...canFly, ...canSwim };}
const duck = createDuck();console.log(duck.fly()); // 'flying'console.log(duck.swim()); // 'swimming'Mixin Pattern
Section titled “Mixin Pattern”// Add methods to objectfunction mixin(target, ...sources) { return Object.assign(target, ...sources);}
const swimmer = { swim() { return 'swimming'; }};
const flyer = { fly() { return 'flying'; }};
const runner = { run() { return 'running'; }};
class Animal { constructor(name) { this.name = name; }}
const penguin = new Animal('Penguin');mixin(penguin, swimmer, flyer, runner);
console.log(penguin.swim()); // 'swimming'console.log(penguin.run()); // 'running'Composition with Factory
Section titled “Composition with Factory”function createPerson(name, behaviors) { return { name, ...behaviors };}
const programmer = { code() { return 'writing code'; }, debug() { return 'fixing bugs'; }};
const teacher = { teach() { return 'teaching'; }, explain() { return 'explaining concepts'; }};
const person = createPerson('John', { ...programmer, ...teacher});
console.log(person.code()); // 'writing code'console.log(person.teach()); // 'teaching'Delegation Pattern
Section titled “Delegation Pattern”class Car { constructor(engine) { this.engine = engine; }
start() { return this.engine.start(); }
stop() { return this.engine.stop(); }}
class Engine { start() { return 'Engine started'; } stop() { return 'Engine stopped'; }}
const car = new Car(new Engine());console.log(car.start()); // 'Engine started'Trait Pattern
Section titled “Trait Pattern”// Traits: Methods with shared behaviorconst hasId = (state) => ({ getId() { return state.id; }, setId(id) { state.id = id; }});
const hasName = (state) => ({ getName() { return state.name; }, setName(name) { state.name = name; }});
function createUser(id, name) { const state = { id, name };
return { ...hasId(state), ...hasName(state), getFullInfo() { return `${this.getName()} (ID: ${this.getId()})`; } };}
const user = createUser(1, 'John');console.log(user.getFullInfo()); // 'John (ID: 1)'Composable Functions
Section titled “Composable Functions”// Pipe and compose for function compositionconst compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x);
const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x);
// Utility functionsconst add5 = x => x + 5;const multiply2 = x => x * 2;const square = x => x * x;
// Compose: multiply2(add5(x))const composed = compose(square, multiply2, add5);console.log(composed(3)); // ((3 + 5) * 2)² = 256
// Pipe: add5, multiply2, squareconst piped = pipe(add5, multiply2, square);console.log(piped(3)); // ((3 + 5) * 2)² = 256Partial Composition
Section titled “Partial Composition”// Build complex behaviors from simple unitsfunction createStore() { const handlers = {};
return { on(event, handler) { handlers[event] = handler; }, emit(event, data) { if (handlers[event]) { handlers[event](data); } } };}
function createLogger(store) { return { onUserLogin(data) { console.log(`User ${data.id} logged in`); store.emit('user:login', data); }, onUserLogout(data) { console.log(`User ${data.id} logged out`); store.emit('user:logout', data); } };}
const store = createStore();const logger = createLogger(store);
store.on('user:login', (data) => { console.log('Processing login:', data);});
logger.onUserLogin({ id: 123, name: 'John' });