Liskov Substitution Principle
Principle
When extending a class, consider that you should be able to pass objects of the subclass in place of objects of the parent class without breaking the client code.
I've stated this principle informally; if you look it up on Wikipedia for instance, you will find formal descriptions such as "Let ϕ(x)
be a property provable about objects x
of type T
. Then ϕ(y)
should be true for objects y
of type S
where S
is a sub-type of T
." All this is a fancy way of saying that every subclass/derived class should be substitutable for their base/parent class.
In other words, a subclass should not break the expectations (code contracts) set by its super-class.1
Let's see an example to better understand the Liskov Substitution Principle (LSP). Consider the following implementation of the Rectangle
class.
public class Rectangle {
private int width;
private int height;
public void setWidth(int width) { this.width = width; }
public void setHeight(int height) { this.height = height; }
public int getArea() { return width * height; }
}
Since a square is a rectangle (mathematically speaking), we decide to implement Square
as a subclass of Rectangle
. We override setWidth
and setHeight
and we can reuse the implementation of getArea
.
public class Square extends Rectangle {
@Override
public void setWidth(int width) {
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(int height) {
super.setWidth(height);
super.setHeight(height);
}
}
With this overriding of
setWidth
andsetHeight
— to set both dimensions to the same value — instances ofSquare
remain mathematically valid squares.
The bad news is that making Square
a subtype of Rectangle
violates the Liskov Substitution Principle. Here is why: consider the following code snippet. LSP is violated because if we pass an object of Square
to clientMethod
the assertion in clientMethod
will fail. In other words, the expected behavior of supertype is not maintained.
void clientMethod(Rectangle rect) {
rect.setWidth(5);
rect.setHeight(4);
assert rect.getArea() == 20;
}
Note we can pass Square
to the clientMethod
because it is a subtype of Rectangle
.
So what is the solution? Well, you can simply make two separate classes: Rectangle
and Square
.
And, you could use composition to reuse the code in Rectangle
:
public class Square {
private Rectangle rectangle;
public void setSideLength(int side) {
rectangle.setHeight(side);
rectangle.setWidth(side);
}
public int getArea() { return rectangle.getArea(); }
}
Case in point
Inheritance seems like a cool idea; you get to create a hierarchy of types and reuse your code. But, it’s easy to abuse inheritance. So, don't overdo it; especially consider composition over inheritance!
1 A closely related principle in software development is called "Design by contract".