设计模式:里氏替换原则
里氏替换原则(Liskov Substitution Principle ,LSP):
指的是任何基类可以出现的地方,子类一定可以出现。
定义1
如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都替换成o2时,程序p的行为没有发生变化,那么类型T2是类型T1的子类型。
定义2
所有引用基类的地方必须能透明地使用其子类对象。
问题由来
有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
解决方案
当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。
里氏替换原则包含了四层含义
1.子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
实践,以枪为例,看一下类图
枪支类图
枪支的抽象类:
public abstract class AbstractGun {public abstract void shoot();
}
手枪,步枪实现类:
public class HandGun extends AbstractGun {public void shoot() {System.out.println("手机射击"); }
}
public class Rifle extends AbstractGun {public void shoot() {System.out.println("步枪射击"); }
}
士兵实现类:
public class Soldier {private AbstractGun gun;public void setGun(AbstractGun gun) {this.gun = gun;}public void killEnemy() {System.out.println("士兵杀敌人");gun.shoot();}
}
场景类:
public class Client {public static void main(String[] args) {Soldier sanMao = new Soldier();sanMao.setGun(new Rifle());sanMao.killEnemy();}
}
注意
在类中调用其他类时务必要使用父类或者接口(例如Solider类的setGun(AbstractGun gun)方法),否则说明类的设计已经违背了LSP原则。
现在有个玩具枪该怎么定义?直接继承AbstractGun类吗?如下:
public class ToyGun extends AbstractGun {@Overridepublic void shoot() {//玩具枪不能像真枪杀敌,不实现}
}
现在的场景类:
public class Client {public static void main(String[] args) {Soldier sanMao = new Soldier();sanMao.setGun(new ToyGun());sanMao.killEnemy();}
}
在这种情况下,士兵拿着玩具枪杀敌,发现业务调用类已经出现了问题,正常的业务逻辑运行结果是不正确的。(因为玩具枪并不能杀敌)ToyGun应该脱离继承,建立一个独立的类,可以与AbstractGun建立关联委托关系。类图如下:
玩具枪与真实枪分离
按照继承的原则,ToyGun继承AbstractGun是没有问题的,但是在具体应用场景中就需考虑:子类是否能够完整地实现父类的业务,否则就会出现上面的情况拿玩具枪杀敌。
注意
如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中发生重写或者重载,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。
2 子类中可以增加自己特性
子类当然可有自己的方法和属性。里氏替换原则可以正着用,但是不能反着用,在子类出现的地方,父类未必可以胜任。
再说下面两层含义之前先要明白 重载 重写(覆盖) 的区别:
重写(覆盖)的规则:
重载的规则:
3 类的方法重载父类的方法时,方法的前置条件(形参)要比父类方法的输入参数更宽松.
实例:
public class Father {public Collection doSomething(HashMap map){System.out.println("父类被执行了");return map.values();}
}
public class Son extends Father{public Collection doSomething(Map map){System.out.println("子类被执行了");return map.values();}
}
public class Client{public static void main(String[] args) {invoker();}public static void invoker(){Son son = new Son();//子类对象HashMap map=new HashMap<>();son.doSomething(map);}
}
运行是”父类被执行了”,这是正确的,父类方法的参数是HashMap类型,而子类的方法参数是Map类型,子类的参数类型范围比父类大,那么子类的方法永远也不会执行。
4 覆写或者实现父类的方法时输出结果(返回值)可以被缩小
父类的一个方法的返回值是一个类型T,子类的相同方法(重载或者重写)的返回值为S,那么里氏替换原则就要求S必须小于等于T。
总结
有子类出现的地方父类未必就可以出现
设计模式:里氏替换原则相关推荐
- 设计模式 里氏替换原则
设计模式 里氏替换原则 继续里氏替换原则. 上回栗子为正方形不能被长方形继承,这次,具体 子类必须实现父类的方法 类图 对具体代码进行实践 // 定义抽象类枪 public abstract clas ...
- 设计模式-里氏替换原则
设计模式-里氏替换原则 优点 面向对象的语言继承必不可少的,有如下优点 代码共享,减少创建类的工作量 提高代码的重用性 提高代码的可扩展性 提高代码的可扩展性 提高产品代码的开放性 继承侵入性 只要继 ...
- 北风设计模式课程---里氏替换原则(Liskov Substitution Principle)
北风设计模式课程---里氏替换原则(Liskov Substitution Principle) 一.总结 一句话总结: 当衍生类能够完全替代它们的基类时:(Liskov Substitution P ...
- 《设计模式》杂记之里氏替换原则
在这篇博文中,我想把自己学习过的里氏替换原则一些好知识点分享给大家.首先我想把继承的一下优缺点给大家分享一下,然后再引出里氏替换原则吧!<?xml:namespace prefix = o ns ...
- 里氏替换原则——举例说明Java设计模式中的里氏替换原则
里氏替换原则--举例说明Java设计模式中的里氏替换原则 1. 前言 官方定义: 2. 举例说明 2.1 例子介绍 2.2 反例 2.2.1 类图说明 2.2.2 代码说明 2.2.3 测试 2.2. ...
- 设计模式六大原则之里氏替换原则、依赖倒置原则详解
设计模式六大原则--里氏替换原则.依赖倒置原则详解 1.里氏代换原则(Liskov Substitution Principle) 概念 顾名思义,该原则用于经常发生替换的地方,在Java中指的是实现 ...
- Java设计模式(03) -- 里氏替换原则
六大设计原则 单一职责原则定义:约定一个类应该有且仅有一个改变类的原因: 开闭原则定义:规定软件中的对象.类.模块和函数对扩展应该是开放的,但对于修改是封闭的,核心思想也可以理解为面向抽象编程. 里氏 ...
- 《设计模式》——里氏替换原则
先扯两句 原本是不想扯了的,因为很久没扯了也不知道该说写什么,可是这里氏替换原则东西实在是太多了,我看过都快一周了,但是每次想写博客的时候,都写几个字就扔下了,倒不是说书中的内容不够详细,只是如果都是 ...
- java设计模式3,里氏替换原则
目录 一.里氏替换原则定义 二.里氏替换原则的作用 三.违背原则场景 四.里氏替换原则改变代码 1.抽象人物类 2.哪吒子类 3.敖丙子类 五.关注公众号哪吒编程,回复1024,获取Java学习资料, ...
- 「设计模式」六大原则之三:里氏替换原则小结
文章目录 1.里式替换原则定义 2. 举例说明 示例1: 示例2: 3. 哪些代码明显违背了 LSP? 子类违背父类声明要实现的功能 子类违背父类对输入.输出.异常的约定 子类违背父类注释中所罗列的任 ...
最新文章
- 安利 10 个 Intellij IDEA 实用插件
- 「AHOI / HNOI2018」转盘 解题报告
- Hadoop学习之Hadoop集群的定制配置(一)
- 使用RMAN验证备份的有效性
- 自己动手开发SAP Spartacus focus Directive的单元测试
- eclipse加速之禁用 JS、jsp 等文件的语法验证
- Flex(flash)检测摄像头的3种状态(是否被占用,没安装摄像头,正常)
- Glassnode:比特币正迎来多年以来最大的流动性枯竭
- 一个 8 岁的“前端老人”
- javaWeb:相关监听方法汇总
- 工作中线程池使用不当的问题记录(get是阻塞式的)
- 讲教资备考时间和精力
- Win10 LTSC 2019进入桌面时假死的拆中处理方法
- 51CTO学院三周年
- FPS能重塑英雄联盟的辉煌吗?Valorant公测半年有什么优势和不足?
- 流媒体(视频)开发常用调试工具
- php html注释多行,css多行注释怎么写
- 元数据管理-技术元数据解决方案
- OCX控件的注册卸载,以及判断是否注册
- php 按位与运算,PHP 按位与()运算符应用实践