里氏替换原则

里氏替换原则

里氏替换原则的优点:

在面向对象的语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点:

  1. 代码共享,减少创建类的工作量,每个子类都拥有父类的属性和方法;
  2. 提高代码的重用性;
  3. 子类可以形似父类,但又异于父类;
  4. 提高代码的可扩展性;提高产品或项目的开放性。

里氏替换原则的缺点:

  1. 继承是侵入性的。只要继承,就必须拥有父类的属性和方法。
  2. 降低代码的灵活性。子类会多一些父类的约束。
  3. 增强了耦合性。当父类的常量、变量、方法被修改时,需要考虑子类的修改。为了让“利”的因素发挥最大的作用,同时减少“弊”带来的麻烦,引入了里氏替换原则(LSP)。

里氏替换原则最正宗的定义是:如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代替o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。

通俗点讲,就是只要父类能出现的地方,子类就可以出现,而且替换为子类也不会产生任何错误或异常。

里氏替换原则包含4层含义

里氏替换原则为良好的继承定义了一个规范,一句简单的定义包含了4层含义。

1. 子类必须完全实现父类的方法。

我们在做系统设计的时候,经常会定义一个接口或抽象类,然后编码实现,调用类则直接传入接口或抽象类,其实这里就已经使用了里氏替换原则。我们以打CS举例,来描述一下里面用到的枪。类图如下:

在这里插入图片描述

枪的主要职责是射击,如何射击在各个具体的子类中实现,在士兵类Soldier中定义了一个方法 killEnemy,使用枪来kill敌人,具体用什么枪,调用的时候才知道。

AbstractGun类源码如下:

在这里插入图片描述

手枪、步枪、机枪的实现类代码如下:

在这里插入图片描述

士兵类的源码为:

在这里插入图片描述

注意,士兵类的killEnemy方法中使用的gun是抽象的,具体时间什么枪需要由客户端(Client)调用Soldier的构造方法传参确定。

客户端Client源码如下:

在这里插入图片描述

**注意:**在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背了LSP原则。

2. 子类可以有自己的个性。

孩子类当然可以有自己的属性和方法了,也正因如此,在子类出现的地方,父类未必就可以代替。

还是以上面的关于枪支的例子为例,步枪有 AK47、SKS狙击步枪等型号,把这两个型号的枪引入后的Rifle的子类图如下:

在这里插入图片描述

SKS狙击步枪可以配一个8倍镜进行远程瞄准,相对于父类步枪,这就是SKS的个性。源码如下:

在这里插入图片描述

狙击手Spinner类的源码如下:

在这里插入图片描述

狙击手因为只能使用狙击枪,所以,狙击手类中持有的枪只能是狙击类型的,如果换成父类步枪Rifle,则传递进来的可能就不是狙击枪,而是AK47了,而AK47是没有zoomOut方法的,所以肯定是不行的。这也验证了里氏替换原则的那一句话:有子类出现的地方,父类未必就可以代替。

3. 覆盖或实现父类的方法时,输入参数可以被放大。

来看一个例子,我们先定义一个Father类:

在这里插入图片描述

然后定义一个子类:

在这里插入图片描述

子类方法与父类方法同名,但又不是覆写父类的方法。你加个@Override看看,会报错的。像这种方法名相同,方法参数不同,叫做方法的重载。你可能会有疑问:重载不是只能在当前类内部重载吗?因为Son继承了Father,Son就有了Father的所有属性和方法,自然就有了Father的doSomething这个方法,所以,这里就构成了重载

接下来看场景类:

在这里插入图片描述

根据里氏替换原则,父类出现的地方子类就可以出现,我们把上面的父类替换为子类:

在这里插入图片描述

我们发现运行结果是一样的。为什么会这样呢?因为子类Son继承了Father,就拥有了doSomething(HashMap map)这个方法,不过由于Son没有重写这个方法,当调用Son的这个方法的时候,就会自动调用其父类的这个方法。所以两次的结果是一致的。

举个反例,如果父类的输入参数类型大于子类的输入参数类型,会出现什么问题呢?我们直接看代码执行结果即可轻松看出问题:

扩大父类方法入参:

在这里插入图片描述

缩小子类方法入参:

在这里插入图片描述

场景类:

在这里插入图片描述

根据里氏替换原则,有父类的地方就可以有子类,我们把Father替换为Son看看结果:

在这里插入图片描述

两次运行结果不一致,违反了里氏替换原则,所以子类中方法的入参类型必须与父类中被覆写的方法的入参类型相同或更宽松。

4. 覆盖或实现父类的方法时,输出结果可以被缩小。

这句话的意思就是,父类的一个方法的返回值是类型T,子类的相同方法(重载或重写)的返回值为类型S,那么里氏替换原则就要求S必须小于等于T(即子类返回值类型必须小于等于父类)。为什么呢?因为重写父类方法,父类和子类的同名方法的输入参数是相同的,两个方法的范围值S小于等于T,这是重写父类方法的要求。

如果对你有帮助,就一键三连呗(点赞+收藏+关注),我会持续更新更多干货~~

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序猿陌名!

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

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

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

打赏作者

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

抵扣说明:

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

余额充值