设计模式七大原则之里氏代换原则(Liskov Substitution Principle)

里氏代换原则(LSP:Liskov Substitution Principle)指出:如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
换句话说,所有引用基类的地方必须能透明地使用其子类的对象。对于子类而言,可以扩展父类的功能,但不能改变父类原有的功能。它包含以下4层含义:

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

还是举一个交通工具的例子

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
/**
* @author sunys
*/
public class Liskovsubstitution {
public static void main(String[] args) {
Transportation transportation = new Transportation();
transportation.run("轮船");
Transportation1 transportation1 = new Transportation1();
transportation1.run("轮船");
transportation1.runSpeed(120);
}
private static class Transportation{
void run(String transportation) {
System.out.println(transportation + " 在水中航行....");
}
}
private static class Transportation1 extends Transportation{
/**
* 覆盖父类的方法
* @param vehicle
*/
@Override
void run(String vehicle) {
System.out.println(vehicle + " 在公路上运行....");
}
void runSpeed(int runSpeed){
System.out.println("行驶速度为:"+ runSpeed);
}
}
}

由上面的例子我们可以看出继承的风险,Transportation类有一个打印交通工具行驶方式行驶方式的功能,且该功能只适用于水中运行的交通工具,刚开始一切正常。但是,现在多了一个新的需求,要打印交通工具的行驶速度,类Transportation1来负责实现,于是Transportation1继承Transportation完成第二个功能,但是在调用者不知道的情况下无意间覆盖(重写)了父类中的方法,导致原本正常的功能发生逻辑错误。改进方式:去掉原有的继承关系,让父类和子类都继承一个更通俗的基类,使用组合方式完成功能。如下:

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
32
33
34
35
36
/**
* @author sunys
*/
public class Liskovsubstitution1 {
public static void main(String[] args) {
TransportationSpeed transportationSpeed = new TransportationSpeed("轮船",120);
transportationSpeed.runSpeed();
}
private static class Transportation{
public String way;
public int speed;
}
private static class TransportationWay extends Transportation{
void run(String transportation) {
System.out.println(transportation + " 在水中航行....");
}
}
private static class TransportationSpeed extends Transportation{
//如果需要使用TransportationWay类的方法,使用组合关系
private TransportationWay transportationWay = new TransportationWay();
public TransportationSpeed(String way,int speed) {
this.way = way;
this.speed = speed;
}

public TransportationSpeed() {

}

void runSpeed(){
//组合调用TransportationWay类run方法获取行驶方式
transportationWay.run(way);
System.out.println("行驶速度为:"+ speed);
}
}
}

里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在实际开发的过程中,使用继承时,我们应当遵循里氏替换原则,在子类中尽量不要重写父类的方法,适当情况下,我们可以通过聚合,组合,依赖等方式来解决问题。

附:本次演示的项目地址
https://github.com/syshlang/java-design-principle