ariko stephen
ariko stephen

Reputation: 123

Usefulness of the substitution property of the Liskov Substitution Principle

The Liskov substitution principle states that a piece of code should remain correct if a superclass is replaced with its subclass (paraphrasing). What I don't understand; Why do we care about this property? Does it make our code more reusable, flexible , or maintainable ?

I would appreciate an example piece of code that adheres to the principle and one that violates it when explaining the relevance of substitution property.

Upvotes: 1

Views: 108

Answers (2)

nik0x1
nik0x1

Reputation: 1461

Violation of the Liskov substitution principle leads to incorrect execution of the program.


Here is an example of such a violation:

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 area() {
        return width * height;
    }
}
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);
    }
}
// This code will not work correctly for instance of Square
void test(Rectangle rectangle) {
    rectangle.setWidth(5);
    rectangle.setHeight(10);

    assertEquals(50, rectangle.area());
}

Upvotes: 1

Mark Seemann
Mark Seemann

Reputation: 233337

The Liskov substitution principle states that a piece of code should remain correct if a superclass is replaced with its subclass

If this was the only thing that the principle stated, it hardly makes sense to ask why we care about it. Consider what the opposite of that principle would mean: That a piece of code should not remain correct if a superclass is replaced with its subclass?

Who would want their software to be incorrect?

What the Liskov Substitution Principle (LSP) actually says is that a a subclass may widen preconditions and tighten postconditions, but not the other way around.

This makes good sense, if you think about it. Consider a simple method that takes an integer as input.

void Foo(int i)

If the superclass states that every integer i is valid, then what would happen if a subclass tightened the precondition? You might, for example, implement a subclass that divides some number by i. If you do that, however, the input value 0 would now cause a division-by-zero error in the subclass, and that error wouldn't happen in the superclass.

Client code relies on the contract published by the superclass, since it doesn't know which subclass it might be interacting with.

As the opposite example, imagine that the superclass has a precondition that says that i mustn't be 0. This means that no well-behaved client is going to call the Foo method with a 0 argument. If you then implement a subclass that can handle 0, no harm is done. Thus, you can widen the precondition, but not tighten it.

Similar considerations apply to postconditions, just in the opposite direction. Here, a subclass may tighten a postcondition, but not widen it.

You can see some realistic C# examples in my article The Liskov Substitution Principle as a profunctor.

Upvotes: 3

Related Questions