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 and setHeight — to set both dimensions to the same value — instances of Square 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".