一、总览

LSP: Liskov Substitution Principle.
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
基本思想:在任意父类出现的地方,都可以直接使用子类来代替。

例:Father 类有方法 f1。为了拓展,实现了 Son 类,在 Son 类中添加方法 f2。那么 Son 应该尽量达到:即使覆写 f1 方法,覆写后的 f1 方法应该和 Father 类的 f1 的预期行为是一致的。这样的话,以前所有调用 Father 类 f1 方法的地方,都可以替换为对 Son 类 f1 方法的调用,而不会出现不一样的结果。

二、举例说明

1、 例:有一个手机类,描述手机有打电话、发短信、看时间功能,用一个类描述这件事。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Phone {
public void call(String number) {
System.out.println("拨打电话:" + number);
}

public void sendSms(String number, String text) {
System.out.println("发送短信给:" + number + ",内容:" + text);
}

public void showTime() {
System.out.println(new Date());
}
}

public class Client {
public static void main(String[] args){
Phone phone = new Phone();
phone.call("1XXXXXXX");
phone.sendSms("2XXXXXXX", "Hello sixlab.");
phone.showTime();
}
}

2、 现在有了智能手机,不仅能打电话,发短信、看时间,还能玩游戏。

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
class SmartPhone extends Phone {
public void call(String number) {
System.out.println("拨打电话:" + number);
}

public void showTime() {
System.out.println(LocalDateTime.now());
}

public void playGame(String gameName) {
System.out.println("玩游戏:" + gameName);
}
}

public class Client {
public static void main(String[] args){
Phone phone = new Phone();
phone.call("1XXXXXXX"); // 此方法的调用 Phone 类可以替换为 SmartPhone 类,因为虽然覆写,但行为是一致的
phone.sendSms("2XXXXXXX", "Hello sixlab."); // 此方法的调用 Phone 类也可以替换为 SmartPhone 类,因为没有覆写,行为一致
phone.showTime(); // 此方法的调用 Phone 类不能替换为 SmartPhone 类,因为覆写后修改了行为,行为不一致

SmartPhone phone1 = new SmartPhone();
phone1.call("1XXXXXXX");
phone1.sendSms("2XXXXXXX", "Hello sixlab.");
phone1.showTime();
phone1.playGame("羞羞的游戏");
}
}

在此例中,如果不考虑showTime方法,那么是符合里氏替换原则的,但是因为SmartPhone类修改了showTime方法的行为,导致与Phone类的此方法行为不一致,所以不符合里氏替换原则。

三、总结

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
  2. 子类中可以增加自己特有的方法。
  3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
  4. 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。