设计模式七大原则—里氏替换原则

1.基本介绍

里斯科瓦(Barbara Liskov)使美国麻省理工学院电气工程于计算机科学系资深教授,她是美国国家工程院院士,在程序语言、分布式计算、程序设计方法及软件工程领域做出了卓越贡献。里斯科瓦于1987年提出了一个关于继承的原则,也就是现在我们称为的“里氏替换原则”。

里氏替换原则基于子类对象可以赋给父类对象的“多态性”,表明子类可以替换父类,并且出现在父类能够出现的任何地方。但是该原则要求继承时需要遵守以下两点:

  • 继承必须确保父类所拥有的“性质”在子类中仍然成立,且子类的程序执行无异常。
  • 子类可以新增特有方法,并且可以重写父类的抽象方法,但不能改变父类原有的方法。

这两点也主要是衡量继承的使用是否符合了里氏替换原则的标准。


 2.继承的滥用

通过一段小故事来讲解,为了让程序达到复用效果,滥用继承且不遵守“里氏替换原则”从而使程序带来意想不到的麻烦。

张三在学习了面向对象的技术后,深刻的感受到继承复用带来的快感,于是使用面向对象技术开发了一款“模拟鸭子游戏”,游戏中会出现各种鸭子,我们可以操作不同的鸭子进行游泳戏水或是呱呱叫。因为它采用了继承的特性,设计了一个鸭子父类,并让不同种类的鸭子都继承此父类。

随着游戏市场的竞争压力加剧,张三为了让自己的“模拟鸭子游戏”在市场上更受欢迎,张三开发扩展了一个新的功能,就是让游戏中所有鸭子可以飞行。此时的张三已经被继承的魅力冲昏了头脑,他想到鸭子都有翅膀,只需要在Duck类中加上Fly()方法,基于继承的特性所有鸭子都会复用这个Fly()方法,他心里想:“妙啊,这可真是一招定乾坤啊”。

“模拟鸭子游戏”在加入飞行功能投入市场后,可怕的问题发生了。某个玩家将一只“橡皮鸭”既然操作飞了起来,这显然不符合一个程序的正常逻辑,因此张三受到了公司的严厉制裁,不仅游戏被下架还被罚款。这是怎么回事?张三为了让程序整体达到复用效果,忽略了极个别的鸭子其实并不具备飞行的能力,这使得某些并不适用该行为(飞行)的子类,也具有了该行为。

这个故事告诉了我们,为了让程序达到复用的效果,继承并不是一个“以逸待劳”的方案,使用继承来达到复用必须要求继承的成员在所有子类中均有统一性。张三之所以没有使用好继承,就是因为他使用的继承过于片面,且没有遵守里氏替换原则。然而,理解里氏替换原则的最终目的,就是促使我们更好的、规范的使用继承,能够明白什么时候什么情况下正确的使用继承,以及其中蕴含的原理。


3.让继承性质合理化

接着张三开发“模拟鸭子游戏”的故事例子,我们来针对其中出现的问题:“即在使用继承过程中,为父类加入新的行为,从而导致某些并不适合该行为的子类,也被迫继承该行为。”来通过UML图的方式,介绍两种如何解决问题的方式。当然方式不止两种,但我们最终目的是要让我们的继承符合“里氏替换原则”。

1.切断不合理继承,扩展新的父类。

设计思路:从Duck类中去除了Fly方法,以此切断了橡皮鸭继承Fly方法的途径,然后新增“动物鸭”类包含Fly的方法,将会飞的鸭子继承之该类,并且可以间接继承Duck类中的共有特性(叫、游泳)。

 

2.组合方式

设计思路:该方式的做法和“继承”不同的地方在于,“Fly方法”不是继承来的,而是通过一个接口“组合”来的。子类可以根据自身情况实例化不同的“飞行接口实现类”来执行相应的飞行功能。该方式相对于第一种较为复杂,因为基本上已经形成了一个设计模式“策略模式”,在该章节对该模式不进行详细介绍,目前只用知道有这种方式即可,后续在对于的专题会详细展开。

posted @ 2022-05-06 16:41  姜承轩  阅读(658)  评论(0编辑  收藏  举报