简单认识里氏替换原则
里氏替换原则(Liskov Substitution Principle LSP):
定义1:如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
定义2:所有引用基类的地方必须能透明地使用其子类的对象。
我比较倾向定义2:所有引用基类的地方必须能透明地使用子类的对象。
个人理解:只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类,但是,反过来就不行了,有子类出现的地方,父类未必就能适应。
适应范围:继承关系
里氏替换原则示例
子类覆盖(重写)父类的方法
我们定义了一个计算类,要求类中主要实现两个数相加。
示例1:
package com.jinit.test;
public class Calculator {
public int count(int a, int b){
return a+b;
}
}
调用:
package com.jinit.test;
public class Caller {
public static void main(String[] args) {
int a = 2;
int b = 3;
Calculator calculator = new Calculator();
int res = calculator.count(a, b);
System.out.println("a+b="+res);
}
}
运行结果:
一切都正常运行。现在来定义一个子类SubClass继承于Calculator类,要求子类实现一个计算两个数相加之后再加10。
示例2
package com.jinit.test;
public class SubClass extends Calculator{
@Override
public int count(int a, int b){
return a+b+10;
}
}
调用:
package com.jinit.test;
public class Caller {
public static void main(String[] args) {
int a = 2;
int b = 3;
SubClass subClass = new SubClass();
System.out.println("父类:a+b="+subClass.count(a, b));
System.out.println("子类:a+b="+subClass.count(a, b));
}
}
运行结果:
可以看出SubClass是继承Calculator类的,按照继承原则,子类SubClass也可以调用父类的计算两个数相加的方法,但是运行结果却出现了错误。
原因就是类SubClass在给方法起名时无意中重写了父类的方法,造成原本运行父类正常的功能出现了错误。
除了重写,重载会出问题吗?
示例3
package com.jinit.test;
import java.util.HashMap;
public class Parent {
public void doSomething(HashMap map){
System.out.println("父类方法执行");
}
}
package com.jinit.test;
import java.util.Map;
public class Son {
public void doSomething(Map map){
System.out.println("子类方法执行");
}
}
package com.jinit.test;
import java.util.HashMap;
public class Caller {
public static void main(String[] args) {
Son son = new Son();
HashMap map1 = new HashMap();
son.doSomething(map1);
}
}
运行结果:
子类在没有重写父类方法的前提下,子类方法被执行了,这会引起业务逻辑混乱。
实际开发中运用情况
我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。
总结
里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。 它包含以下4层含义:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。(示例3)
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。(重写的要求)
采用里氏替换原则的目的就是增强程序的健壮性,版本升级时也可以保持非常好的兼容性,即使增加子类,原有的子类还可以继续运行。