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 walk
a Bird
's default mode of travel
. Finally, we can introduce the Penguin
class — all without breaking migrate_south
.