Software design · intermediate

Composition vs inheritance

Composition builds behavior by holding and delegating to collaborating components, while inheritance defines a subtype relationship that acquires or overrides behavior from a parent type. The choice is about coupling and substitutability, not merely code reuse.

Why it matters

Composition makes dependencies replaceable and capabilities independently combinable. Inheritance is valuable when the subtype truly preserves a stable parent contract and clients should use it anywhere the parent is expected.

Mental model

How to reason about composition vs inheritance

Ask two different questions: does this object use another capability, or is it behaviorally a specialized form of the parent abstraction? Use composition for the first; consider inheritance for the second only when substitutability remains sound.

Analogy

A computer is composed from replaceable parts, while a laptop is a subtype of computer only if it honors what users of the computer abstraction are promised.

Examples

See the boundary, not just the happy path

Worked example · Inject a composed capability

class ReportService { ReportService(this.store); final ReportStore store; }

ReportService delegates persistence to a ReportStore collaborator. Tests can supply an in-memory implementation without forcing the service into the store's type hierarchy.

Worked example · Use an interface for substitutability

class Circle implements Shape { double area() => pi * r * r; }

Circle can stand in for Shape when it honors Shape's full behavioral contract. An interface relationship expresses substitutability without inheriting storage implementation.

Avoid · Inherit only to reuse implementation

class Car extends Engine

A car uses an engine but is not behaviorally an engine. Inheritance exposes irrelevant parent operations and couples Car to Engine internals; a contained Engine models the relationship.

Common mistakes

Misconceptions to remove early

Applying prefer composition as an absolute ban

Inheritance and interface conformance are appropriate for stable subtype relationships and framework extension points. The principle warns against inheritance used only as a reuse shortcut.

Assuming composition removes coupling

A component still depends on its collaborator's interface and behavior. Narrow contracts and dependency direction reduce coupling; merely storing another concrete object does not.

Quick check

Can you predict the result?

1. Which question most strongly supports inheritance?
  • Can the subtype honor the parent contract everywhere the parent is expected?
  • Can the parent save a few copied lines of code?
  • Does the subtype happen to contain similarly named fields?
Answer: Can the subtype honor the parent contract everywhere the parent is expected?
2. Why is composition useful for testing a service?
Answer: A narrow collaborator can be replaced with a test implementation without changing the service's inheritance hierarchy.

Keep building

Authoritative references

Make the idea retrievable.

Study this concept with spaced repetition, next to the commands where you use it.

Get Terminaster