查看“设计模式之禅 里氏替换原则”的源代码
←
设计模式之禅 里氏替换原则
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您所请求的操作仅限于该用户组的用户使用:
用户
您可以查看和复制此页面的源代码。
=== 爱恨纠葛的父子关系 === :在面向对象的语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点: :* 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性; :*提高代码的重用性; :*子类可以形式父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,“世界上没有两片完全相同的叶子”是指明子与父的不同; :*提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口都是通过继承父类来完成的; :*提高产品或项目的开放性。 :自然界的所有事物都是优点和缺点并存的,即使是鸡蛋,有时候也能挑出骨头来,继承的缺点如下: :*继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法; :*降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束; :*增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果——大段的代码需要重构。 :Java 使用 extends 关键字来实现继承,它采用了单一继承的规则,C++ 则采用了多重继承的规则,一个子类可以继承多个父类。 从整体上来看,利大于弊,怎么才能让“利”的因素发挥最大的作用,同时减少“弊”带来的麻烦呢?解决方案是引入里氏替换原则(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.(如果对每一个类型为 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.(所有引用基类的地方必须能透明地使用其子类的对象。) :第二个定义是最清晰明确的,通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。 但是,反过来就不行了,有子类出现的地方,父类未必就能适应。 === 纠纷不断,规则压制 === :里氏替换原则为良好的继承定义了一个规范,一句简单的含义包含了4层含义。 ====子类必须完全实现父类的方法==== :我们在做系统设计时,经常会定义一个接口或抽象类,然后编码实现,调用类则直接传入接口或抽象类,其实这里已经使用了里氏替换原则。 我们举个例子来说明这个原则,大家都打过 CS 吧,非常经典的 FPS 类游戏,我们来描述一下里面用到的枪,类图如图所示: [[文件:CS游戏中的枪支类图.png|居中|缩略图|685x685像素]] :枪的主要职责是射击,如何射击在各个具体的子类中定义,手枪是单发射程比较近,步枪威力大射程远,机枪用于扫射。 在士兵类中定义了一个方法 killEnemy,使用枪来杀敌人,具体使用什么枪来杀敌人,调用的时候才知道,AbstractGun 类的源程序如代码所示:<syntaxhighlight lang="java"> /** * 枪支的抽象类 */ public abstract class AbstractGun { //强用来干什么的?杀敌! public abstract void shoot(); } </syntaxhighlight> :手枪、步枪、机枪的实现类如代码所示: <syntaxhighlight lang="java"> /** * 手枪的实现类 * */ public class Handgun extends AbstractGun { //手枪的特点是携带方便,射程短 @Override public void shoot() { System.out.println("手枪射击..."); } } </syntaxhighlight><syntaxhighlight lang="java"> /** * 步枪的实现类 */ public class Rifle extends AbstractGun{ //步枪的特点是射程远,威力大 @Override public void shoot() { System.out.println("步枪射击..."); } } </syntaxhighlight><syntaxhighlight lang="java"> /** * 机枪的实现类 */ public class MachineGun extends AbstractGun{ @Override public void shoot() { System.out.println("机枪扫射..."); } } </syntaxhighlight> :有了枪支,还要有能够使用这些枪支的士兵,其源程序如代码所示: <syntaxhighlight lang="java"> /** * 士兵的实现类 */ public class Soldier { //定义士兵的枪支 private AbstractGun gun; //给士兵一支枪 public void setGun(AbstractGun gun) { this.gun = gun; } public void killEnemy() { System.out.println("士兵杀敌..."); gun.shoot(); } } </syntaxhighlight> :注意“public void killEnemy() {”,定义士兵使用枪来杀敌,但是这把枪是抽象的,具体是手枪还是步枪需要在上战场(也就是场景中)前通过 setGun 方法确定。场景类 Client 的源代码如代码所示: <syntaxhighlight lang="java"> /** * 场景类 */ public class Client { public static void main(String[] args) { //产生一个士兵 Soldier soldier = new Soldier(); //给士兵一支枪 soldier.setGun(new Rifle()); soldier.killEnemy(); } } </syntaxhighlight>
返回至
设计模式之禅 里氏替换原则
。
导航菜单
个人工具
登录
名字空间
页面
讨论
变种
视图
阅读
查看源代码
查看历史
更多
搜索
导航
首页
Spring Boot 2 零基础入门
Spring Cloud
Spring Boot
设计模式之禅
VUE
Vuex
Maven
算法
技能树
Wireshark
IntelliJ IDEA
ElasticSearch
VirtualBox
软考
正则表达式
程序员精讲
软件设计师精讲
初级程序员 历年真题
C
SQL
Java
FFmpeg
Redis
Kafka
MySQL
Spring
Docker
JMeter
Apache
Linux
Windows
Git
ZooKeeper
设计模式
Python
MyBatis
软件
数学
PHP
IntelliJ IDEA
CS基础知识
网络
项目
未分类
MediaWiki
镜像
问题
健身
国债
英语
烹饪
常见术语
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息