里氏代换原则(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
|
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{
@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
|
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{ private TransportationWay transportationWay = new TransportationWay(); public TransportationSpeed(String way,int speed) { this.way = way; this.speed = speed; }
public TransportationSpeed() {
}
void runSpeed(){ transportationWay.run(way); System.out.println("行驶速度为:"+ speed); } } }
|
里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在实际开发的过程中,使用继承时,我们应当遵循里氏替换原则,在子类中尽量不要重写父类的方法,适当情况下,我们可以通过聚合,组合,依赖等方式来解决问题。
附:本次演示的项目地址
https://github.com/syshlang/java-design-principle