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.