面向对象设计原则之里氏替换原则

里氏替换原则(Liskov Substitution Principle,LSP)

If for each object o1 of type S there is an object o2 of type T such
that for all programs P defined in terms of T,the behavior of P is
unchanged when o1 is substituted for o2 then S is a subtype of T.

概念1:就定义了类型T的程序P而言,如果每一个类型为S的对象o1都有一个类型为T的对象o2,当o2被o1替代时,程序P的行为并不会产生变化,那么类型S是T的子类型。

Functions that use pointers or references to base classes must be able
to use objects of derived classes without knowing it.

概念2:引用了基类型的方法必须能够透明的使用其子类型对象。

概念有点绕,简而言之里氏替换原则个人理解就是使用父类的地方能够被子类替代,并且不会改变程序的行为。

基于这个原则,就会要求在编写代码时需要遵守以下事项:

  • 子类可以重写父类的抽象方法,但不能重写父类的非抽象方法
  • 子类方法的形参需要比父类方法的形参更加宽松
  • 子类的输出需要比父类的输出更加严格
  • 子类可以新增自己的特性

举个例子:
面向对象设计原则之里氏替换原则
面向对象设计原则之里氏替换原则

ok,代码很简单,我们先测试一下。
面向对象设计原则之里氏替换原则

运行结果非常深得我心。
面向对象设计原则之里氏替换原则
但此时,如果代码稍微改一下,妈妈生了一个女儿
面向对象设计原则之里氏替换原则

再修改一下测试类
面向对象设计原则之里氏替换原则

运行一下试试看:
面向对象设计原则之里氏替换原则

完了,出问题了。虽然也不能说爸爸和女儿睡一块不正常,但是还是觉得…反正我相信你们懂得哈哈哈。

到这里,我们大概可以总结一下这一个不符合里氏替换原则的小案例,程序若想透明的使用子类,或者说子类替换了引用父类的地方,若想保证程序语义不发生畸形,那么子类不能重写父类的非抽象方法。就比如上面的情况,如果子类重写了父类的方法,那就可能会发生爸爸和女儿睡在一起这种情况。

即我们可以把女儿的行为改变一下:
面向对象设计原则之里氏替换原则
女儿就有了自己新的特性。

当然,子类重写父类的方法,形参要更加宽松。就好比子类形参是Map,而父类是HashMap,此时调用输入一个HashMap的话两个都能符合,但最终调用的是父类的方法。

里氏替换原则的优缺点

优点:

  • 代码共享,即公共代码被抽到父类。
  • 提高代码重用性。
  • 子类在父类的基础上可以有自己的特性。
  • 提高代码的扩展性。

缺点:

  • 侵入性。一旦继承,父类全部属性和方法都被子类拥有
  • 约束性。子类需要拥有父类的属性和方法,子类多了一些约束。
  • 耦合性。父类出现修改情况时,需要考虑子类的修改。