里士替换原则 (Liskov Substitution Principle)

本文介绍了里氏替换原则,强调了在Java编程中遵循历史替换原则的重要性,通过代码示例展示了遵循原则的正确实践和不遵循的后果。文章还讨论了优点如提高代码复用性和健壮性,以及缺点如侵入性和耦合性增强。
摘要由CSDN通过智能技术生成

1. 概念

  • 任何基类可以出现的地方,子类一定可以出现
    在这里插入图片描述

2. 为什么要遵循历史替换原则

  • 继承关系给程序带来侵入性
  • 保证程序升级后的兼容性
  • 避免程序出错

3. 规范

  • 原则
    • 保证基类所拥有的性质在子类中仍然成立
    • 子类扩展父类的功能,但是不能改变父类原有的功能
  • 如何规范地遵循里氏替换原则
    • 子类必须完全实现父类的抽象方法,但不能覆盖父类的非抽象方法
      在这里插入图片描述

    • 子类可以实现自己特有的方法
      在这里插入图片描述

    • 当子类的方法实现父类抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格
      在这里插入图片描述

    • 子类的实例可以替代任何父类的实例,但反之不成立
      在这里插入图片描述


4. 代码示例

示例1
  • 不遵循历史替换原则导致程序问题
    • 误重写了一个父类的方法导致了程序的问题
package com.bz.design.principle.liskov;

public class Liskov001 {

    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));
        System.out.println("1-8=" + b.func1(1, 8));
        System.out.println("11+3+9=" + b.func2(11, 3));
    }
}
class A {
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}
class B extends A {
    public int func1(int num1, int num2) {
        return num1 + num2;
    }
    public int func2(int num1, int num2) {
        return func1(num1, num2) + 9;
    }
}
示例1代码问题分析
  • 我们发现原来运行正常的相减功能发生了错误。原因就是:类B无意中重写了父类的方法,造成原有功能出现错误。在实际编程中,我们常常会过通过重写父类的方法完成新的功能,这样写起来虽然简单,但整个继承体系的复用性会比较差。特别是运行多态比较频繁的时候。
  • 解决办法(代码参考示例2)
    • 通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖,聚合,组合等关系代替。
示例2
package com.bz.design.principle.liskov.improve;

public class LiskovImprove001 {

    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类,因此调用者,不会再func是求减法
        // 调用完成的功能就会很明确
        System.out.println("11-3=" + b.func3(11, 3));
        System.out.println("1-8=" + b.func3(1, 8));
        System.out.println("11+3+9=" + b.func2(11, 3));
    }
}
// 创建一个更加基础的基类
class Base {
    // 把更加基础的方法和成员写到Base类
}
class A extends Base {
    public int func1(int num1, int num2) {
        return num1 - num2;
    }
}
class B extends Base {
    public int func1(int num1, int num2) {
        return num1 + num2;
    }
    public int func2(int num1, int num2) {
        return func1(num1, num2) + 9;
    }
    // 如果B需要使用A类的方法,使用组合关系
    private A ao = new A();
    public int func3(int num1, int num2) {
        return this.ao.func1(num1, num2);
    }
}

5. 优缺点

  • 优点
    • 约束继承泛滥,它也是开闭原则的一种很好的体现。
    • 提高了代码的重用性。
    • 降低了系统的出错率。类的扩展不会给原类造成影响,降低了代码出错的范围和系统出错的概率。
    • 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险。
  • 缺点
    • 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。
    • 降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束。
    • 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果 --> 大段的代码需要重构。
  • 继承实际上让两个类耦合性增强了,在适当的情况下,可以通过聚合,组合,依赖来解决问题

6. 注意事项

  • 里氏替换原则 = 父类能被子类替换 = 继承复用的规范
  • 注意
    • 不遵守规范 => 当前代码没问题,未来出错率会提高
    • 聚合/组合 > 继承(合成复用原则)
  • 18
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BrightChen666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值