1、什么是里氏替换原则
Liskov于1987年提出了一个关于继承的原则“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“继承必须确保父类所拥有的性质在子类中仍然成立。”也就是说,当一个子类的实例应该能够替换任何其父类的实例时,它们之间才具有is-A关系。该原则称为Liskov Substitution Principle——里氏替换原则。
2、代码示例
正方形不是长方形:
在我们的认知范围内,长方形的长不等于宽,正方形是长等于宽的长方形,正方形是一种特殊的长方形。但在实际编码过程中遇到的情况是怎么样的呢,通过代码分析:
package cn.com.design.model.test.liskvorole.negative;
/**
* 项目名称:OpenCloseRole
* 类 名 称:Test
* 类 描 述:TODO
* 创建时间:2021/1/18 下午8:32
* 创 建 人:wteng
*/
/**
* 在我们的认知范围内,一致认为正方形就是一个特殊的长方形
* 我们也知道,两个类只要有"is a"关系,两个类就可以发生继承关系。
* 里氏替换原则:任何使用父类的地方,都能被透明的替换成子类。替换成子类后,程序行为不会发生问题。
* 在里氏替换原则的指导方针下,可得出:仅仅依据两个类之间有没有"is a"的关系,来判断两个类能不能发生继承关系,是不够的,
*
* 以下是反例,正方形不能替换长方形,替换后测试失败
*/
class Rect {
private int width;
private int height;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int area() {
return getHeight()*getWidth();
}
}
class Squaer extends Rect{
private int sideLength;
@Override
public void setWidth(int width) {
this.sideLength = width;
}
@Override
public void setHeight(int height) {
this.sideLength = height;
}
@Override
public int getWidth() {
return sideLength;
}
@Override
public int getHeight() {
return sideLength;
}
}
class Foo {
public void testArea(Rect rect) {
rect.setWidth(10);
rect.setHeight(15);
if ((rect.area() == 150)) {
System.out.println("测试通过");
} else {
System.out.println("测试失败");
}
}
}
public class Test {
public static void main(String[] args) {
Foo foo = new Foo();
// Rect rect = new Rect();
Rect squer = new Squaer();
foo.testArea(squer);
}
}
但是运行结果为测试失败,说明正方形是特殊的长方形违背了里氏替换原则,为了解决上边问题,仅仅断绝square和Rect的继承关系即可
除此之外,类似经典的案例还有鸵鸟飞鸟。
3.总结
所谓对象是一组状态和一系列行为的组合。状态是对象的内在特性,行为是对象的外在特性。LSP所表述的就是在同一个继承体系中的对象应该有共同的行为特征。我们在设计对象时是按照行为进行分类的,只有行为一致的对象才能抽象出一个类来。设置长方形的长度的时候,它的宽度保持不变,设置宽度的时候,长度保持不变。正方形的行为:设置正方形的长度的时候,宽度随之改变;设置宽度的时候,长度随之改变。所以,如果我们把这种行为加到基类长方形的时候,就导致了正方形无法继承这种行为。我们“强行”把正方形从长方形继承过来,就造成无法达到预期的结果。鸵鸟非鸟,能飞是鸟的特性,但鸵鸟是不能飞的,我们强行将其归为鸟类,最终导致代码出错。
所有子类的行为功能必须和其父类持一致,如果子类达不到这一点,那么必然违反里氏替换原则。在实际的开发过程中,不正确的派生关系是非常有害的。伴随着软件开发规模的扩大,参与的开发人员也越来越多,每个人都在使用别人提供的组件,也会为别人提供组件。最终,所有人的开发的组件经过层层包装和不断组合,被集成为一个完整的系统。每个开发人员在使用别人的组件时,只需知道组件的对外裸露的接口,那就是它全部行为的集合,至于内部到底是怎么实现的,无法知道,也无须知道。所以,对于使用者而言,它只能通过接口实现自己的预期,如果组件接口提供的行为与使用者的预期不符,错误便产生了。里氏替换原则就是在设计时避免出现派生类与基类不一致的行为。