Simpler Alternatives: Closures, Higher-Order Functions, Composition
What This Concept Is
Many classical OO patterns were invented in languages where functions were not first-class. When functions are first-class (JavaScript, Python, Kotlin, Scala, Go, Rust, modern Java with lambdas), a pattern often collapses to:
- a closure (a function that carries state by capture)
- a higher-order function (a function that takes or returns another function)
- plain composition (passing one object to another through a constructor)
Classic collapses:
- Strategy -> pass the algorithm as a function
- Command -> a thunk or a lambda
- Observer -> a list of callback functions
- Template Method -> a function that takes the varying steps as parameters
- Factory Method -> a function that returns the right value
- Iterator -> a language feature (
for ... of,yield,Iterator) - small Decorators -> a wrapper function that returns a new function
The point is not "OO is obsolete." The point is that in languages with first-class functions, the minimum viable design is often a function, and the class-based pattern is worth adding only when a function is genuinely insufficient.
Why It Matters Here
Many "design pattern" conversations are symptoms of a mismatch between a language feature and a solution shape. You can save a reader dozens of lines of boilerplate -- and dozens of minutes of cognitive load -- by choosing the smaller shape when both work.
This concept teaches you to identify those collapse cases and to defend the richer class-based form when it genuinely earns its cost.
Concrete Example
Pattern-heavy version (OO Strategy):
interface DiscountStrategy { int apply(int cents); }
class LoyaltyDiscount implements DiscountStrategy { public int apply(int c){ return (int)(c*0.9); } }
class EmployeeDiscount implements DiscountStrategy { public int apply(int c){ return (int)(c*0.8); } }
int price(Cart cart, DiscountStrategy s) { return s.apply(cart.subtotal()); }
Closure version:
const loyalty = cents => Math.round(cents * 0.9);
const employee = cents => Math.round(cents * 0.8);
function price(cart, discount) { return discount(cart.subtotal()); }
They encode the same design. The closure version:
- has no named type
- cannot be extended by subclass
- cannot be introspected by name
If none of those losses matter, the closure wins on readability. If you need pluggable policies with names, serialization, or multi-method contracts, the class form earns its cost.
A second example: a Decorator as a higher-order function.
const withRetry = (fn, n = 3) => async (...args) => {
let lastErr;
for (let i = 0; i < n; i++) {
try { return await fn(...args); }
catch (e) { lastErr = e; }
}
throw lastErr;
};
const robustFetch = withRetry(fetch, 5);
That is a Decorator. No classes, no interfaces, no boilerplate.
Common Confusion / Misconception
- "Functional" is not the opposite of "object-oriented." Most modern codebases mix both fluently.
- A closure is not inherently simpler than a class. It is simpler when the class would have been a one-method shell; it is not simpler when multiple related methods, naming, or inheritance matter.
- Collapsing a pattern to a function can lose useful things: names in stack traces, type checking in some languages, discoverability. Decide consciously.
- Not every language supports every collapse cleanly. Java lambdas are more constrained than Python closures; type systems differ; pick the tool your team actually uses.
How To Use It
When you feel the urge to introduce a pattern, ask:
- Does the pattern have exactly one method? -> consider a function.
- Does the pattern only store one piece of state? -> consider a closure.
- Is "decorating" the behavior the only reason for the class? -> consider a higher-order function.
- Is inheritance doing real work, or just being used for code reuse? -> consider composition + delegation instead.
- If you still want the pattern, write both; keep the simpler one unless the richer one names something the team actually needs.
Check Yourself
- What does the class-based form give you that a closure does not?
- Why does "Template Method" often collapse to a function that takes callbacks?
- When is composing small functions worse than one honest class?
Mini Drill or Application
Take three real patterns from this module (say, Strategy, Decorator, and small Factory). Do two things per pattern:
- Write the pattern in both styles: class-based and function-based.
- Commit to one choice for a hypothetical team and write two sentences justifying it in terms of your language, team size, and the specific change pressure.