Video summary
Why the Best Codebases Barely Use Inheritance Anymore ?
Main summary
Key takeaways
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
- Add delivery-related behavior as abstract methods (conceptually):
-
Result (when requirements match)
- Shared methods on the parent work for all children.
- The correct
ship/trackbehavior 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
shipeven though “shipping” doesn’t exist - You must implement
track, but tracking a download is impossible - Workarounds:
- implement
shipin a “fake” way (e.g., send download link) - implement
trackby throwing an exception (“don’t call this”)
- implement
- You still must implement
-
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/trackout of the original baseOrder - Make physical order subclasses extend
PhysicalOrder, whileDigitalOrderextendsOrderdirectly
- Create an intermediate parent like
-
Fallout
- You trigger refactor across the codebase because other parts assumed every
Orderhadship.
- You trigger refactor across the codebase because other parts assumed every
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
Orderas essentially data only- No delivery assumptions
- No abstract
ship/trackmethods
2) Move delivery into separate collaborating objects
- Create independent delivery classes:
GroundShipperAirShipperFreightShipper
- Each shipper:
- contains a method such as
ship(order) - takes the order as an input parameter rather than being the order itself
- contains a method such as
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
- create
- 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
- For an order that includes a physical item plus a digital license:
- 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.
- services could hold a reference to the parent type (
- With composition, there’s no parent class to unify behavior.
- Solution: introduce an interface (lightweight contract) such as:
DeliveryMethod- requires a method like
deliver()
- requires a method like
- 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
- Android: extending
- 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)
- Android framework (extending