The USB Port Test for the Liskov Substitution Principle
A second, sharper example of the Liskov Substitution Principle beyond birds, plus the two real requirements a working fix for the flying-bird problem still needs.
The penguin problem showed what a Liskov Substitution Principle violation looks like: a subclass that technically compiles but breaks the promise its parent made. Before going further, it's worth testing that idea against a completely different example, one with no birds in it at all.
A Database and a USB Port Look Similar, and Aren't
Picture a Database interface with a connect() method and a disconnect() method. MySqlDatabase and PostgresDatabase each implement it their own way. Other code just asks for a Database and calls connect(). It never needs to know which one it actually has.
Now here's the trap. A USB port has something you could also call connect() and disconnect(): plugging something in, unplugging it. Should UsbPort implement the Database interface too, since the method names already match?
No. And the reason is the entire point of this principle.
connect() on a Database means one thing: open a session so you can run queries. connect() on a UsbPort means something completely different: complete an electrical connection so a device can talk to the machine. Same name. Different meaning.
Hand a UsbPort to code that expects a Database, and it breaks. Not because the method is missing, but because calling it does something nobody asked for.
That's the real test: not "does the child have a method with the right name," but "does the child actually do what the parent promised."
Back to the Birds: Where the Design Still Falls Short
That same test is what broke the bird hierarchy. Forcing Penguin to have a fly() method was UsbPort.connect() all over again: a method that exists but breaks its promise.
Splitting into FlyingBird and NotFlyingBird fixed that one problem. But two others are still open: class explosion, and no single type for "every bird that can fly."
What an Actual Fix Has to Deliver
The class-per-combination approach from before satisfies the first requirement and fails the second. Whatever comes next has to hold onto that first win while actually fixing the second.
Keep reading