设计模式六大原则之二: 里氏替换原则.
简介
姓名 : 里氏替换原则
英文名 :Liskov Substitution Principle
座右铭 :
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.
如果对每一个类型为 S 的对象 o1, 都有类型为 T 的对象 o2, 使得以 T 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时, 程序 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 个定义来自《设计模式之禅》, 比较干巴巴, 不认真思考起来可能不太容易懂. 简单来说就是定义了什么是父子. 在现实生活中, 什么是父子? 就是生你的那个男人和你的关系就是父子 (父女). 而这里定义的就是假如 A 能胜任 B 干的所有事情, 那 B 就是 A 的父亲, 也就是儿子要会父亲的所有能活, 儿子活得再烂也要有父亲的水平.
价值观 : 很显然, 比较传统, 严父出孝子. 儿子必须要有父亲的能耐, 最好青出于蓝胜于蓝.
伴侣 : 估计有个贤惠的老婆, 才能有这么优秀的儿子.
个人介绍 : 我比较严厉, 也是为了生存没办法, 只有一辈一辈地变优秀, 一直坚持下去, 家族就会越来越好. 这样就可以富过三代, 你看你们人类不是经常说富不过三代... 扎心了老铁, 老子还是富零代.
老爹开车, 前方注意
里氏替换原则定义了什么是父子, 还有一点要注意的, 就是儿子不能在父亲会的技能上搞 "创新".
比如父亲会做红烧排骨, 儿子在新东方烹饪学校中学到了一招, 在红烧排骨里面加糖和醋, 变成红烧糖醋排骨, 更加美味, 看代码, 儿子在父亲的基础红烧排骨上加了糖醋, 好像没啥问题.
- class Father1 {
- public void braisedRibs(){
- System.out.println("红烧排骨");
- }
- }
- class Son1 extends Father1 {
- public void braisedRibs(){
- System.out.println("红烧糖醋排骨");
- }
- }
运行下面代码, 会打印: 红烧排骨
- Father1 father1 = new Father1();
- father1.braisedRibs();
我们上面说过, 所有在使用父亲的地方, 都能够替换成儿子, 并且效果是一样的, 那接下来我们改一下代码.
- Son1 son1 = new Son1();
- son1.braisedRibs();
结果是啥? 打印出: 红烧糖醋排骨, 出乎意料吧... 这结果完全不一样. 想一下上面说的: 老爸会的老子也要会, 很明显, 上面的例子老子不会红烧排骨, 只会红烧糖醋排骨, 所以这根本不是父子关系.
那应该怎么实现呢? 其实红烧排骨和红烧糖醋排骨这压根就是 2 道菜, 你去餐馆吃饭的时候, 你点红烧排骨服务员给你送来红烧糖醋排骨, 或者你点红烧糖醋排骨服务员给你送来红烧排骨, 你这时候不生气, 算我输.
来看看 Son2,Son2 将红烧糖醋改为 braisedSweetAndSourPorkRibs (翻译不好找 Google 算账去哈, 反正不是我翻译的).
- class Son2 extends Father1 {
- public void braisedSweetAndSourPorkRibs(){
- System.out.println("红烧糖醋排骨");
- }
- }
测试一下是不是好儿子
- Son2 son2 = new Son2();
- son2.braisedRibs();
- son2.braisedSweetAndSourPorkRibs();
打印出:
红烧排骨
红烧糖醋排骨
这才是 Father1 的好儿子嘛, 不仅会红烧排骨, 还会红烧糖醋排骨. 所以说里氏替换原则就是在定义父子关系, 大家都遵守这个定义, 就会一代比一代好, 不遵守大家也看到了, 把前辈传下来的都毁于一旦了.
代码见:
优缺点
下面再贴一下书本上的一些优缺点
优点
代码共享, 减少创建类的工作量, 每个子类都拥有父类的方法和属性;
提高代码的重用性;
子类可以形似父类, 但又异于父类,"龙生龙, 凤生凤, 老鼠生来会打洞" 是说子拥有父的 "种","世界上没有两片完全相同的叶子" 是指明子与父的不同;
提高代码的可扩展性, 实现父类的方法就可以 "为所欲为" 了, 君不见很多开源框架的扩展接口都是通过继承父类来完成的;
提高产品或项目的开放性.
缺点
继承是侵入性的. 只要继承, 就必须拥有父类的所有属性和方法;
降低代码的灵活性. 子类必须拥有父类的属性和方法, 让子类自由的世界中多了些约束;
增强了耦合性. 当父类的常量, 变量和方法被修改时, 需要考虑子类的修改, 而且在缺乏规范的环境下, 这种修改可能带来非常糟糕的结果 ---- 大段的代码需要重构.
(来自《设计模式之禅》)
总结
好了, 里氏替换原则的大概原理讲得差不多, 大家只要记住是在定义 "父子关系", 就像游戏规则一样, 定义后让大家遵守, 会让大家的程序在后面越来越复杂的时候也能清晰, 而不会越来越乱.
参考资料:《大话设计模式》,《Java 设计模式》,《设计模式之禅》,《研磨设计模式》,《Head First 设计模式》
来源: http://www.bubuko.com/infodetail-2901022.html