本文来了解一下什么是里氏替换原则。

定义

  • 如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型T2时类型T1的子类型。
  • 一个软件实体如果适用于一个父类的话,那么一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。(所以避免子类覆盖父类的方法,而是子类新增方法满足实现)
  • 引申意义:子类可以扩展父类的功能,但不能改变父类原有的功能
  • 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法
  • 子类中可以增加自己特有的方法
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松
  • 当子类的方法实现父类的方法时(重写/重载或实现实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类返回值更严格或相等

例子1

我们都知道,正方形是特殊的长方形,那么正方形是长方形的子类。但是对于某种场景,这种继承关系是不符合里氏替换原则的。我先定义一个长方形类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Rectangle {
private int width;
private int length;

public int getWidth() {
return width;
}

public void setWidth(int width) {
this.width = width;
}

public int getLength() {
return length;
}

public void setLength(int length) {
this.length = length;
}
}

再定义一个正方形类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Square extends Rectangle{
private int sideLength;

public int getSideLength() {
return sideLength;
}

public void setSideLength(int sideLength) {
this.sideLength = sideLength;
}

@Override
public int getWidth() {
return getSideLength();
}

@Override
public void setWidth(int width) {
this.setLength(width);
}

@Override
public int getLength() {
return getSideLength();
}

@Override
public void setLength(int length) {
this.setLength(length);
}
}

测试,场景是判断宽度小于等于长度的话,宽度则加一。测试长方形是OK的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Test {

public static void resize(Rectangle rectangle){
while(rectangle.getWidth() <= rectangle.getLength()){
rectangle.setWidth(rectangle.getWidth()+1);
System.out.println("宽度为:"+rectangle.getWidth()+",长度为:"+rectangle.getLength());
}
}

public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setWidth(10);
rectangle.setLength(20);
resize(rectangle);
}
}

但是测试正方形的时候,就会死循环了,因为一直都满足这个条件。也就是说正方形这个子类替换掉父类的长方形类时,程序出错了,不满足里氏替换原则了。

例子2

子类的方法入参要比父类的宽松。

定义一个基类Base:

1
2
3
4
5
public class Base {
public void hello(HashMap map){
System.out.println("base is running");
}
}

定义一个子类Child:

1
2
3
4
5
6
7
8
9
10
public class Child extends Base{
@Override
public void hello(HashMap map) {
System.out.println("child hashmap is running...");
}

public void hello(Map map){
System.out.println("child map is running...");
}
}

测试:

1
2
3
4
5
6
7
public class Test {
public static void main(String[] args) {
Child child = new Child();
HashMap map = new HashMap();
child.hello(map);
}
}

结果肯定打印: child hashmap is running…

但是如果我将子类重写的方法删除呢?结果是: base is running

如果反过来,父类入参是Map,子类重载的方法入参是HashMap,那么就会执行这个重载的方法。在业务开发中,这样子可能会造成业务逻辑的混乱。