2.2.6 · D3Design Principles

Worked examples — SOLID — Liskov Substitution Principle

2,572 words12 min readBack to topic

The scenario matrix

Before any example, let us list every shape a substitution problem can take. Think of a subclass method as making four promises to a caller who only knows the base. Each promise can be kept or broken — and there are a few degenerate/limiting cases too.

Cell Case class What varies Worked in
A Precondition widened (legal ✅) subclass accepts more inputs Ex 1
B Precondition strengthened (violation ❌) subclass rejects inputs base accepted Ex 2
C Postcondition strengthened (legal ✅) subclass promises more Ex 3
D Postcondition weakened (violation ❌) subclass promises less Ex 4
E Invariant broken (violation ❌) hidden relationship changed Ex 5
F New exception thrown (violation ❌, rule 3) subclass throws what base never did Ex 6
G Degenerate / zero input empty, null, 0-size — does the base contract even say what happens? Ex 7
H Limiting / edge value max size, boundary — subclass silently caps it Ex 8
I Real-world word problem payment processors Ex 9
J Exam twistlooks like a violation but is legal return-type narrowing (covariance) Ex 10

Figure — SOLID — Liskov Substitution Principle

The diagram above is our compass. Green arrows = directions a subclass is allowed to move a promise. Red arrows = forbidden. Preconditions may only widen (open the door wider); postconditions may only narrow (promise more). We will hit every arrow below.


Worked examples





Figure — SOLID — Liskov Substitution Principle

The figure above draws the two runs side by side: on the left a lavender Rectangle where setHeight(4) leaves width at 5 (area 20); on the right a coral Square where the same setHeight(4) secretly drags width down to 4 (area 16). Follow the coral arrow labelled "same caller code" — identical caller instructions, yet the invariant collapses on the right and the proved area drops from 20 to 16. That silent gap of 4 is the whole violation made visible.







Recall

Recall Which cells are

violations and which are legal? Legal ::: A (widen precondition), C (strengthen postcondition), G (define undefined case), J (covariant return). Violations ::: B (strengthen precondition), D (weaken postcondition), E (break invariant), F (new exception), H (limit-value weakening).

Recall Rectangle→Square: expected vs actual area for the 5×4 test

Expected ::: 20 (a real Rectangle). Actual with Square ::: 16, because setHeight(4) also resets width to 4, so 4×4.

Recall Why is Ex 10's return-type narrowing allowed but Ex 2's precondition narrowing forbidden?

Because ::: return types are outputs (postcondition, may narrow/covariant); parameters are inputs (precondition, may only widen/contravariant). Only inputs surprise a caller when tightened.


See also: Design by Contract (where pre/post/invariant come from), Polymorphism (what LSP protects), SOLID — Interface Segregation Principle (the usual fix for Cells F & I), and the sibling principles SOLID — Single Responsibility Principle, SOLID — Open Closed Principle, SOLID — Dependency Inversion Principle.