Skip to content
Plurafy

Composition and OOP Patterns

Combining functions and objects for flexible code

Choose between inheritance and composition, combine behaviors flexibly, and avoid brittle hierarchies.

Composition is often superior to inheritance for flexibility and maintainability.

// ❌ Inheritance: Brittle hierarchy
class 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 combinations
const 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'
// Add methods to object
function 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'
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'
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'
// Traits: Methods with shared behavior
const 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)'
// Pipe and compose for function composition
const compose = (...fns) => (x) =>
fns.reduceRight((v, f) => f(v), x);
const pipe = (...fns) => (x) =>
fns.reduce((v, f) => f(v), x);
// Utility functions
const 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, square
const piped = pipe(add5, multiply2, square);
console.log(piped(3)); // ((3 + 5) * 2)² = 256
// Build complex behaviors from simple units
function 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' });