When did people favor composition over inheritance?
Perceived Problems with Inheritance
- Inheritance is seen as “white‑box reuse”: subclasses see and depend on internals, leading to fragile base classes and the “unstable base class” problem.
- Deep hierarchies hide where behavior and state actually live; later changes in a parent can unpredictably affect many descendants, especially with co‑recursive call chains (parent → overridden method in child → other parent methods).
- Multiple inheritance and diamonds are cited as a historical source of pain (especially in C++), though some say this is now culturally discouraged or rare.
- Inheritance encourages modeling real‑world taxonomies (“Car → SportsCar → BrandX”) that break when reality changes (new regulations, new types), forcing constant hierarchy surgery.
- Dynamic languages where “everything is overridable” amplify these risks; tools and type checkers can help, but don’t remove the design problem.
Arguments in Favor of Composition
- Composition is viewed as “black‑box reuse”: objects talk via interfaces and only rely on public APIs, supporting change and refactoring better.
- It keeps code paths explicit: you see which collaborators are used instead of inheriting a large, implicit surface area.
- It aligns better with modularity, low coupling, and “read‑optimized code”: more boilerplate for the writer, less surprise for future readers.
- Many patterns (State, Strategy, decorators, ECS in games, role/mixin systems) are essentially structured composition or delegation.
Nuanced / Pro‑Inheritance Views
- Several commenters distinguish type/interface inheritance (good for polymorphism and contracts) from implementation inheritance (brittle).
- Some find inheritance ideal for small, sealed hierarchies and GUI or framework scaffolding, where you design the hierarchy yourself.
- Others argue “prefer composition” has become a dogma or thought‑terminating cliché; they advocate “use both, with judgment”.
Language & Historical Context
- Early languages and research (e.g., CLU, Smalltalk) already emphasized interfaces, abstraction, encapsulation, and composition‑like patterns.
- Newer mainstream languages (Go, Rust) omit class inheritance entirely, relying on interfaces/traits, procedural abstraction, and code generation/macros.
- Kotlin’s delegation, COM‑style interfaces, mixins, roles, and traits are cited as middle grounds that blur inheritance vs composition.
Design Principles & Pedagogy
- Discussion branches into SOLID, with disagreement: some report real wins from applying it; others find parts (especially SRP, OCP) vague or misguiding.
- Several criticize classic OOP teaching (shapes, animals, vehicles) for pushing ontological hierarchies instead of behavior‑first, compositional design.