设计模式之禅02里氏替换原则

112 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情

里氏替换原则

2.1 爱恨纠葛的父子关系

在面向对象的语言中,继承是必不可少的、非常优秀的语言机制,Java使用extends关键字来实现继承,它采用了单一继承的规则。继承有利有弊,整体来说,利大于弊,怎么才能让“利”的因素发挥最大作用,同时减少“弊”带来的麻烦?解决方案是引入里氏替换原则,什么是里氏替换原则:

第一种定义:如果对每一个类型为S的对象o1都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。

第二种定义:所有引用基类的地方必须能透明地使用其子类的对象

第二个字义是最清晰明确的,通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。但是反过来就不行了,有子类出现的地方,父类未必就能适应。

2.2 纠纷不断,规则压制

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

2.2.1 子类必须完实现父类的方法

在具体应用场景中要考虑一个问题:子类是否能够完整地实现父类的业务。

2.2.2 子类可以有自己的个性

里氏替换原则可以正着用,但是不能反过来用。在子类出现的地方,父类未必就可以出现。

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

里氏替换原则要求制定一个契约,也就是父类或接口,契约制定了也就同时制定了前置条件和后置条件,前置条件就是你要让我执行,就必须满足我的条件(方法中的输入参数);后置条件就是我执行完了需要反馈,标准是什么。

2.2.4 覆写或实现父类的方法时输出结果可以被缩小

父灰的一个方法的返回值是一个类型T,子类的相同方法的返回值为S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类。

采用里氏替换原则的目的就是增强程序的健壮性,版本升级时可以保持非常好的兼容性。即使增加子类,原有的子类还可以继续运行。在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑。

2.3 最佳实践

在项目中采用里氏替换原则时,尽量避免子类的“个性”,一旦有“个性”,这个子类和父类之间的关系就很难调和了,把子类当做父类使用,子类的“个性”被抹杀了;把子类单独作为一个gotl来使用,则会让代码间的耦合关系变得扑朔迷离----缺乏类替换的标准。