里氏替换原则

本贴最后更新于 2131 天前,其中的信息可能已经时移世易

景

设计模式六大原则之二:里氏替换原则。

简介

姓名 :里氏替换原则

英文名 :Liskov Substitution Principle

座右铭

  1. 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 的子类型。

  2. 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 的好儿子嘛,不仅会红烧排骨,还会红烧糖醋排骨。所以说里氏替换原则就是在定义父子关系,大家都遵守这个定义,就会一代比一代好,不遵守大家也看到了,把前辈传下来的都毁于一旦了。

代码见:LSPTest.java

优缺点

下面再贴一下书本上的一些优缺点

优点

  1. 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
  2. 提高代码的重用性;
  3. 子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同;
  4. 提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的;
  5. 提高产品或项目的开放性。

缺点

  1. 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
  2. 降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
  3. 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果————大段的代码需要重构。
    (来自《设计模式之禅》)

总结

好了,里氏替换原则的大概原理讲得差不多,大家只要记住是在定义“父子关系”,就像游戏规则一样,定义后让大家遵守,会让大家的程序在后面越来越复杂的时候也能清晰,而不会越来越乱。

参考资料:《大话设计模式》、《Java 设计模式》、《设计模式之禅》、《研磨设计模式》、《Head First 设计模式》

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3453 回帖 • 201 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖 • 1 关注
  • 里氏替换原则
    1 引用
  • LSP
    2 引用 • 1 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...