THE SOLID HARBOUR · CHAPTER 3 OF 5
A replacement type must preserve the behaviour that callers are entitled to expect from the original contract.
01 / MEET THE PRINCIPLE
First understand the idea. Then explore why it helps, before bringing it to life in the harbour.
WHERE THE IDEA BEGAN
Barbara Liskov discussed data abstraction and hierarchy in 1987. Her work with Jeannette Wing formalised behavioural subtyping in their 1994 paper A Behavioral Notion of Subtyping.
02 / WHY IT MATTERS
Useful whenever callers depend on a base class or interface: storage providers, payment adapters and test doubles must preserve the agreed behaviour.
Callers can rely on the contract without asking which implementation they received. This includes valid inputs, promised outputs and relevant state rules—not just method signatures.
A caller passes a valid input and gets an unexpected refusal, missing result or changed state. Defensive type checks then spread through code that should have been able to trust the abstraction.
KNOW THE TRADE-OFF
This is not a rule that every real-world category needs inheritance. Composition or a smaller, accurate contract may be better. Expected, documented errors do not automatically violate LSP.
Next, step into the story and see this problem through a familiar situation.
03 / ONCE UPON A CHANGE
The crossing promises to carry any parcel up to 20 kg and issue a delivery receipt. The harbour swaps its ferry for a tiny rowboat that quietly refuses anything above 5 kg. Both are boats, but the replacement breaks the promise.
Sharing a name or shape is not enough. A substitute must honour the same accepted inputs and promised results. Give the rowboat a different contract if it cannot do that job.
Turn to “Try it” to change the design and see what happens.
THE HARBOUR CROSSING
04 / PULL THE PAPER TAB
The replacement secretly accepts only up to 5 kg.
JavaScript is needed for the simulation. The story, code and practical notes remain available below.
Choose a scenario, then try the change.
An illustrative model, not a benchmark or a claim that every design change causes a bug.
05 / FROM STORY TO CODE
// Contract: weights from 0 to 20 kg are accepted. Receipt Cross(decimal kg) { if (kg > 5m) throw new NotSupportedException(); return Deliver(kg); // breaks the 20 kg promise }
A MORE FOCUSED DESIGN
// A conforming implementation keeps the contract. Receipt Cross(decimal kg) { if (kg < 0m || kg > 20m) throw new ArgumentOutOfRangeException(); return Deliver(kg); // returns the promised receipt }
Illustrative C# sketches. Supporting types and method bodies are omitted; these snippets are not executed in the page.
06 / MAKE IT STICK
Think of a part of your application that is difficult to change or test. Would this principle address the actual cause? What would become simpler, and what extra structure would it introduce?
Try the question opposite, then continue to the next principle.
CHECK YOUR INSTINCT
SOLID is a set of design guidelines, not a scorecard. The aim is software that stays understandable as real requirements change.