Liskov Substitution Principle Discussion
Hard to believe you said all that about birds and types and didn't mention ducks ;)
It's not only the initial class design that led to a LSP violation, it's also the function using the classes. Both made the error to assume that all birds can fly.
Having to change the contract in that
migrate_south can't use generic Birds anymore is exactly what the LSP is supposed to prevent. So, instead of treating the symptom, let's refactor the abstraction, especially the interface, correctly. The key is that all birds can travel.
def migrate_south(birds) birds.each do |bird| bird.travel(AFRICA) end end class Bird def travel(destination) walk(destination) end private def walk(destination) end end class Emu < Bird end class FlyingBird < Bird def travel(destination) fly(destination) end private def fly(destination) end end class Eagle < FlyingBird end class Penguin < Bird def travel(destination) if (walking_distance?(destination)) walk(destination) else book_trip(destination) end end private def walking_distance?(destination) end def book_trip(destination) end end
The refactoring would first introduce the
travel method which simply calls
fly, so altering
migrate_south to use
travel instead of
fly won't change the app behaviour. Then we introduce the
FlyingBird; thanks to the LSP we can make it the
Eagle's new parent class without problems. We also make
Bird's default mode of
travel. Finally, we can introduce the
Penguin class — all without breaking
You'll need to throw an exception for penguins already located at the south pole.