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());
}
如果这是该原则所陈述的唯一内容,那么问我们为什么关心它就毫无意义了。考虑一下该原则的反义词意味着什么:如果用子类替换超类,那么一段代码就不应该保持正确?
谁希望自己的软件不正确?
里氏替换原则 (LSP) 实际上是说,子类可以放宽先决条件并收紧后置条件,但反之则不行。
如果你仔细想想,这很有道理。考虑一个以整数作为输入的简单方法。
如果超类规定每个整数
i
都是有效的,那么如果子类收紧前提条件会发生什么?例如,您可以实现一个将某个数字除以 的子类i
。但是,如果您这样做,输入值0
现在会在子类中导致除以零的错误,而该错误不会发生在超类中。客户端代码依赖于超类发布的契约,因为它不知道它可能与哪个子类交互。
作为相反的例子,假设超类有一个前提条件,规定
i
不能是0
。这意味着没有行为良好的客户端会使用参数调用该Foo
方法0
。如果您随后实现一个可以处理 的子类0
,则不会造成任何损害。因此,您可以扩大前提条件,但不能收紧它。类似的考虑也适用于后置条件,只是方向相反。在这里,子类可以收紧后置条件,但不能放宽它。
您可以在我的文章《里氏替换原则作为函数器》中看到一些真实的 C# 示例。
违反里氏替换原则会导致程序执行不正确。
以下是此类违规行为的一个例子: