SO[L]ID - Liskov Substitution Principle
- 25 June 2017
- Ruby Design
- SO[L]ID - Liskov Substitution Principle
Hi guys, let's continue learning SOLID principles. Today we will talk about Liskov Substitution principle. The principle, that Barbara Liskov defined in 1987 in her conference keynote named "Data abstraction and hierarchy".
Definition says:
if S is a subtype of T, then objects of type T may be replaced with objects of type S
If I had to rephrase definition of Liskov Substitution Prinicple for Ruby programming language, I would define it like this:
If class Man is inherited from class Human, then objects of class Human may be replaced with objects of class Man
It might not be as accurate as original one, because original definition mentions "types" and "subtypes". But we use Duck Typing in Ruby:
if it looks like a duck and quacks like a duck, it's a duck
Let's try to come up with some examples.
First of all we need to define basic class, let's call it Human
:
class Human
def talk
''
end
def height
''
end
end
Now we can define couple "subtypes":
class HomoHabilis < Human
def talk
'Agrrr!'
end
def height
'1.29m'
end
end
class HomoSapiens < Human
def talk
'Hello!'
end
def height
'1.70m'
end
end
Now we should be able to use these subtypes instead of basic type Human
:
habilis = HomoHabilis.new
sapiens = HomoSapiens.new
def introduce_human(human)
puts "Hi, I'm #{human.height} height and I say #{human.talk}"
end
introduce_human(habilis) # => Hi, I'm 1.29m height and I say Agrrr!
introduce_human(sapiens) # => Hi, I'm 1.70m height and I say Hello!
Just a side note: because we have Duck Typing, we could create class which wouldn't be inherited from basic class Human
, but could implement the same interface: height
and talk
and that would work too. That's where polymorphism kicks in.
So far we used inheritance and that allowed us to substitute objects of parent class with objects of inherited classes. How could we break Liskov Substitution Principle then? Any inherited class would have all methods from basic class, so we should be able to substitute one by another.
To understand that I want to show you description of this principle from Wikipedia:
Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called (strong) behavioral subtyping
I like that part: (strong) behavioral subtyping.
Programming languages with strong typing have less chances to break Liskov substitution principle. Because they strictly define types of method arguments and returning values.
In case of Ruby we are responsible for that. We can easily break Liskov substitution principle by changing returning type and that would make substitution impossible:
class HomoSapiens < Human
def talk
'Hello!'
end
def height
{ men: '1.70m', women: '1.62' }
end
end
Now HomoSapiens
returns hash instead of string for height
. That might break code that expects string
from height
method call.
Inheritance works when it's a is-a
relation type. So HomoSapiens
is a Human
, that works. But wrong usage of inheritance would break Liskov Substitution Principle as well:
class HomoHabilis < Human
def talk
'Agrrr!'
end
def height
'1.29m'
end
def social_security_number
raise NotImplementedError
end
end
class HomoSapiens < Human
def talk
'Hello!'
end
def height
'1.70m'
end
def social_security_number
"AAA-GG-SSSS"
end
end
In this case we can not substitute HomoSapiens
by any object of class Human
or its "subtypes" (HomoHabilis
) because those don't respond to social_security_number
properly, which might break our application. In this case wrong usage of inheritance breaks Liskov Substitution Principle.
It's interesting to see how all SOLID principles are related to each other and to basic ideas of OOP: polymorphism, inheritance, 'is-a' vs 'has-a' types of relations between classes.
I hope that helped to understand the idea of this principle and showed how to keep objects of the same types and subtypes substitutable.
Thanks for reading!
Read more about SOLID Principles in case if you missed it: