The caller code never changes. Pass a Rectangle and it should hold its promise β pass a Square and watch what happens to the width you set.
βΈWatch the box animate when you pass a Square: setHeight(4) quietly drags the width down too.
the caller Β· fixed code
bird.fly();
// expect it takes off
pass in a substitute
what the bench sees
π¦
a Bird, ready to fly()
β pick a substitute
The substitution test
The base Bird promises fly() works. A Sparrow keeps that promise. A Penguin can't fly β so what does its fly() do?
βΈA subtype that overrides a method just to throw is a degenerate override β a loud LSP smell.
the corrected design β every substitute passes
We stop forcing bad extends trees. Shapes share a tiny immutableShape interface (just area(), no setters to break). Birds split into a plain Bird and a separate FlyingBird capability. Click any substitute below β they all keep their promises now.
β Substitutability restored
Pick a substitute above to see why it now passes the bench with no surprises.
βΈThe rule: when a subtype can't keep the base's full contract, prefer a shared interface or composition over inheritance.