Video summary

Why the Best Codebases Barely Use Inheritance Anymore ?

Main summary

Key takeaways

Educational

Main ideas / lessons

  • Default advice vs. missing explanation

    • Many senior devs say “prefer composition over inheritance”, but often don’t explain why.
  • Both inheritance and composition aim to solve “code reuse”

    • The goal: write logic once, then use it elsewhere without copy-pasting.

Example methodology: inheritance first → show it break → rebuild with composition

1) Reuse with inheritance

  • Create a base class: Order

    • Stores core order data (e.g., order ID, items, total)
    • Provides shared behavior like:
      • calculate total
      • generate invoice
  • Model delivery differences with inheritance

    • Add delivery-related behavior as abstract methods (conceptually):
      • ship
      • track
    • Create subclasses:
      • StandardOrder
        • ships via ground courier
        • tracks via courier API
      • ExpressOrder
        • ships next-day air
      • InternationalOrder
        • ships freight + handles customs paperwork
  • Result (when requirements match)

    • Shared methods on the parent work for all children.
    • The correct ship/track behavior is chosen automatically via polymorphism.

2) New requirement breaks the inheritance design

  • Add digital orders (non-physical goods)

    • Example: software licenses, ebooks
    • Customer receives a download link by email
  • What goes wrong with inheritance

    • You still must implement ship even though “shipping” doesn’t exist
    • You must implement track, but tracking a download is impossible
    • Workarounds:
      • implement ship in a “fake” way (e.g., send download link)
      • implement track by throwing an exception (“don’t call this”)
  • Why this is fragile

    • Inheritance requires you to predict what all future child types will share.
    • When that prediction fails, the hierarchy becomes painful.
  • Fix attempted: split the hierarchy

    • Create an intermediate parent like PhysicalOrder
    • Move ship/track out of the original base Order
    • Make physical order subclasses extend PhysicalOrder, while DigitalOrder extends Order directly
  • Fallout

    • You trigger refactor across the codebase because other parts assumed every Order had ship.

3) Key metaphor about inheritance fragility

  • Inheritance is like gluing a toy’s parts into one fused object.
  • Later you need a version without one part (e.g., no engine), and you must hacksaw/rework the whole structure.
  • Bottom line: change breaks class hierarchies when assumptions about shared behavior are wrong.

Alternative approach: rebuild with composition

1) Make Order just a data model

  • Redefine Order as essentially data only
    • No delivery assumptions
    • No abstract ship / track methods

2) Move delivery into separate collaborating objects

  • Create independent delivery classes:
    • GroundShipper
    • AirShipper
    • FreightShipper
  • Each shipper:
    • contains a method such as ship(order)
    • takes the order as an input parameter rather than being the order itself

3) Add digital delivery cleanly

  • When digital orders are needed:
    • create DigitalDelivery
    • implement a method like deliver(order)
      • generates a download link
      • emails it to the customer
  • No forced methods, no exceptions, no base-class restructure.
  • Existing code that creates or uses other parts isn’t affected.

4) Enable mixing behaviors (composability)

  • Because delivery behaviors are independent components, you can combine them:
    • For an order that includes a physical item plus a digital license:
      • call ground shipper to ship the physical item
      • call digital delivery to send the license key/download
  • You can also vary behavior per use case in different parts of the app.

Extra concept: restoring “abstraction” with interfaces

  • Inheritance provided a “natural contract”:
    • services could hold a reference to the parent type (Order) and rely on shared methods.
  • With composition, there’s no parent class to unify behavior.
  • Solution: introduce an interface (lightweight contract) such as:
    • DeliveryMethod
      • requires a method like deliver()
    • Concrete classes implement it:
      • ground shipper
      • air shipper
      • digital delivery
  • Then services can depend on the interface, not on specific implementations.

Methodology name: dependency injection (explained in-context)

  • Once you have the interface/contract:
    • the service should not decide which delivery implementation to create
    • instead, the appropriate delivery object is passed in from the outside
  • This is described as:
    • Dependency Injection
    • “Pass in the thing you depend on rather than creating it yourself”
  • No special framework is required—conceptually it’s just providing the dependency.

Tradeoffs and when inheritance can still be appropriate

Downsides of composition (acknowledged)

  • More boilerplate:
    • initializing collaborating objects
  • Sometimes writing wrapper/forwarding methods
  • But benefit:
    • less coupling → less friction when requirements change

When inheritance might make sense

  • If a framework expects it (examples given):
    • Android: extending Activity
    • Java: creating custom exceptions
  • If many classes need mostly the same boilerplate and only override a small part
  • Guidance if using inheritance:
    • design parent classes with what’s meant to be inherited
    • keep internals private/final where possible
    • avoid letting children reach into parent internals directly
    • provide a clean, explicit override API

Bottom-line advice

  • When in doubt, compose.
  • Inheritance can still be useful in constrained scenarios, but composition is generally more resilient to change.

Speakers / sources featured

  • Primary speaker: The YouTube video narrator/creator (no name provided in the subtitles)
  • Referenced concept/examples sources (not speakers):
    • Android framework (extending Activity)
    • Java (custom exceptions)
    • YouTube (as a place the viewer might watch generic examples)

Original video