4.2 里氏替换原则

里氏替换原则是面向对象设计的基本原则之一,它规定子类必须能够替换其基类在任何出现的地方。这一原则确保了软件系统的稳定性和扩展性,避免因子类重写父类方法导致的潜在问题。实践中,遵循LSP意味着子类可以扩展功能但不应改变原有行为,保证方法的输入输出范围。示例展示了如何在代码中应用这一原则,以增强程序的健壮性和可维护性。

1.里斯替换原则的定义

0.里氏替换原则(Liskov Substitution Principle, LSP) 由麻省理工学院计算机科学实验室的里斯科夫(Liskov)女士在1987年的面向对象技术的高峰会议(OOPSLA)上发表的一篇文章《数据抽象和层次》里提出:继承必须确保超类所拥有的性质在子类中仍然成立。

1. 继承包含这样一层含义:父类中凡是已实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有子类必须遵循这些契约,

但如果子类对这些已实现的方法任意修改,就会对整个继承体系造成破坏;

2.继承在给程序设计带来便利的同时,也带来了弊端,如使用继承会给程序带来侵入性,程序的可移植行降低, 增加对象间的耦合性,

若一个类被他类继承,则当该类需修改时,必须考虑到其所有子类,且父类修改后,所有涉及到的子类的功能都有可能产生故障;

3.问题提出:在编程中,如何正确使用继承? =>里氏替换原则:所有引用基类的地方必须透明地使用其子类的对象

核心思想:子类继承父类时尽量不重写父类的方法, 如果不能做到子类中所有父类方法不被改变,则可通过聚合,组合,依赖来解决,

即将两者共同部分再提升一个类,这个类只写两者相同部分; (构建扩展性更好的系统)

2.里式替换原则的作用

1.里式替换原则是实现开闭原则的重要方式之一。

2.它克服了继承中重写父类造成的可复用性变差的缺点;

3.它是动作正确性的保证,即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。

4.加强程序的健壮性,同时变更时可做到非常好的兼容性,提高程序的维护性、可扩展性、降低需求变更时引入的风险;

1.优点

*1.代码重用,减少创建类的工作量,每个子类都拥有父类的方法和属性;

*2.提高代码的延展性,实现父类的方法就可以了,许多开源框架接口都是通过继承父类来完成的;

*3.提高产品或项目的开放性;

2.缺点

*1.继承是侵入性的,只要继承,即必须拥有父类所有的方法和属性;

*2.造成子类代码冗余,降低代码的灵活性,子类必须拥有父类的所有方法和属性,让子类有了一些约束;

*3.增加了耦合性,当父类的常量,变量和方法被修改时,需要考虑子类的修改,重构大量代码;

3.里式替换原则的实现方式

里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。

*1.子类必须完全实现父类的方法;(定义一个接口或抽象类,然后编码实现,调用类则直接传入接口或抽象类)

*2.子类可以有自己的特性(子类可以定义其他的方法或属性);

*3.当子类方法重载父类方法时 方法的前置条件(即方法的输入参数) 取值范围 或 类族范围一定要大于等于父类中该参数的范围;

由于父类能出现的地方,子类必然能出现,如果子类实现的父类方法中的参数取值范围缩小,可能就会越界;

*4.当子类的方法重写/重载或实现父类方法时,方法的后置条件(方法的输出/返回值) 类族范围要小于等于父类;(子类可替换父类)

4.示例

1.类图

2.相关代码

/**
 * 里氏替换原则示例
 * @author xuezhihui
 * @date 2020/11/25 19:46
 */
object Liskov {
    @JvmStatic
    fun main(args: Array<String>) {
        val a: A = A()
        val b: B = B()
        println("11-3=" + a.func1(11, 3))
        //因为B类不在继承A,所以b再调用func1时,一定是用自己的
        println("11+3=" + b.func1(11, 3))
        //使用组合仍然可以使用A类的func1方法
        println("11-3="+b.func3(11,3))
    }
}

//创建一个更加基础的基类
open class Base {
    //把更加基础的方法 和 成员写到 Base类
}

open class A : Base() {
    //返回两个数的差
    open fun func1(num1: Int, num2: Int): Int {
        return num1 - num2
    }
}

//增加了一个新功能,完成两个数相加再加9
class B : Base() {
    //如果B需要使用A类的方法,使用组合关系
    //这里,重写了A类的方法,可能是无意识的
    var a: A = A()
    fun func1(num1: Int, num2: Int): Int {
        return num1 + num2
    }

    fun func2(a: Int, b: Int): Int {
        return func1(a, b) + 9
    }

    //我们仍然使用A的方法
    fun func3(a: Int, b: Int): Int {
        return this.a.func1(a, b) //使用组合关系调用a的方法
    }
}

5.总结

里氏替换原则核心思想就是建立抽象,通过抽象建立规范,具体的实现在运行时替换掉抽象,保证系统的扩展性,

灵活性,开闭原则与里氏替换原则一般是相伴而生的,通过里氏替换来达到对扩展开放, 对修改关闭的效果;

关于里氏替换原则的例子,最有名的是“正方形不是长方形”。当然,生活中也有很多类似的例子,例如,企鹅、鸵鸟和几维鸟

从生物学的角度来划分它们属于鸟类;但从类的继承关系来看,由于它们不能继承“鸟”会飞的功能,故它们不能定义成“鸟”的子类。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值