开启掘金成长之旅!这是我参与「掘金日新计划 · 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来使用,则会让代码间的耦合关系变得扑朔迷离----缺乏类替换的标准。