1.基本介绍
- 里氏替换原则(Liskov Substitution Principle)在1988年,由麻省理工学院的姓里的女士提出的
- 如果对每个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所以程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。换句话说:所有引用基类的地方必须能透明地使用其子类的对象
- 在使用继承时,遵循里氏替换原则,在子类中尽量不要重写父类的方法
-继续实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题
2.应用案例
-
采用继承方式覆盖父类的方法
public class LiskovSubstitution { public static void main(String[] args) { A a = new A(); System.out.println("11-3=" + a.func1(11, 3)); System.out.println("1-8=" + a.func1(1, 8)); System.out.println("-----------------------"); B b = new B(); System.out.println("11-3=" + b.func1(11, 3));//这里本意是求出11-3 System.out.println("1-8=" + b.func1(1, 8));// 1-8 System.out.println("11+3+9=" + b.func2(11, 3)); } } // A类 class A { // 返回两个数的差 public int func1(int num1, int num2) { return num1 - num2; } } // B类继承了A // 增加了一个新功能:完成两个数相加,然后和9求和 class B extends A { //这里,重写了A类的方法, 可能是无意识 public int func1(int a, int b) { return a + b; } public int func2(int a, int b) { return func1(a, b) + 9; } } 复制代码
运行结果:
11-3=8 1-8=-7 ----------------------- 11-3=14 1-8=9 11+3+9=23 复制代码
由运行结果可看出,由于B继承A并且重写了父类(A)的func1方法,造成所有运行相减功能的代码全部调用了类B重写后的方法,造成原本运行正常的功能出现了错误。在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。
里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
-
改进后的代码
public class LiskovSubstitution { public static void main(String[] args) { A a = new A(); System.out.println("11-3=" + a.func1(11, 3)); System.out.println("1-8=" + a.func1(1, 8)); System.out.println("-----------------------"); B b = new B(); //因为B类不再继承A类,因此调用者,不会再func1是求减法 //调用完成的功能就会很明确 System.out.println("11+3=" + b.func1(11, 3));//这里本意是求出11+3 System.out.println("1+8=" + b.func1(1, 8));// 1+8 System.out.println("11+3+9=" + b.func2(11, 3)); //使用组合仍然可以使用到A类相关方法 System.out.println("11-3=" + b.func3(11, 3));// 这里本意是求出11-3 } } //创建一个更加基础的基类 class Base { //把更加基础的方法和成员写到Base类 } // A类 class A extends Base { // 返回两个数的差 public int func1(int num1, int num2) { return num1 - num2; } } // B类继承了A // 增加了一个新功能:完成两个数相加,然后和9求和 class B extends Base { private A a = new A(); //重写A类的方法 public int func1(int a, int b) { return a + b; } public int func2(int a, int b) { return func1(a, b) + 9; } //我们仍然想使用A的方法 public int func3(int a, int b) { return this.a.func1(a, b); } } 复制代码
修改后,我们通过B类使用其父类(A)原有的方法,这样就不会出现错误
3.注意事项和细节
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
- 子类中可以增加自己特有的方法
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END