六大设计原则

单一职责原则定义:约定一个类应该有且仅有一个改变类的原因;

开闭原则定义:规定软件中的对象、类、模块和函数对扩展应该是开放的,但对于修改是封闭的,核心思想也可以理解为面向抽象编程。

里氏替换原则定义:继承必须确保超类所拥有的性质在子类中仍然成立。


里氏替换原则概述:如果S是T的子类型,那么所有T类型的对象都可以在不破坏程序的情况下被S类型的对象替换。简单来说,子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:当子类继承父类时,除添加新的方法且完成新增功能外,尽量不要重写父类的方法。这句话包括了四点含义:·

  • 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
  • 子类可以增加自己特有的方法;
  • 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松;
  • 当子类的方法实现父类的方法(重写、重载或实现抽象方法)时,方法的后置条件(即方法的输出或返回值)要比父类的方法更严格或与父类的方法相等。

里氏替换原则的作用:

  • 里氏替换原则是实现开闭原则的重要方式之一;
  • 解决了继承中重写父类造成的可复用性变差的问题;
  • 是动作正确性的保证,即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性;
  • 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险。

示例

假设在构建银行系统时,储蓄卡是第一个类,信用卡是第二个类。为了让信用卡可以使用储蓄卡的一些方法,选择由信用卡类继承储蓄卡类,讨论是否满足里氏替换原则产生的一些要点。

违背原则方案

储蓄卡和信用卡在使用功能上类似,都有支付、提现、还款、充值等功能,也有些许不同,例如支付,储蓄卡做的是账户扣款动作,信用卡做的是生成贷款单动作。下面这里模拟先有储蓄卡的类,之后继承这个类的基本功能,以实现信用卡的功能。

储蓄卡

public class CashCard {private Logger logger = LoggerFactory.getLogger(CashCard.class);/*** 提现* @param orderId 单号* @param amount  金额* @return 状态码 0000成功、0001失败、0002重复*/public String withdrawal(String orderId, BigDecimal amount) {// 模拟支付成功logger.info("提现成功,单号:{} 金额:{}", orderId, amount);return "0000";}/*** 储蓄** @param orderId 单号* @param amount  金额*/public String recharge(String orderId, BigDecimal amount) {// 模拟充值成功logger.info("储蓄成功,单号:{} 金额:{}", orderId, amount);return "0000";}/*** 交易流水查询* @return 交易流水*/public List<String> tradeFlow() {logger.info("交易流水查询成功");List<String> tradeList = new ArrayList<String>();tradeList.add("100001,100.00");tradeList.add("100001,80.00");tradeList.add("100001,76.50");tradeList.add("100001,126.00");return tradeList;}
}

在储蓄卡的功能实现中包括了三个方法:提现、储蓄、交易流水查询,这些是模拟储蓄卡的基本功能。接下来通过继承储蓄卡的功能,实现信用卡服务。

信用卡

public class CreditCard extends CashCard {private Logger logger = LoggerFactory.getLogger(cn.bugstack.design.CashCard.class);@Overridepublic String withdrawal(String orderId, BigDecimal amount) {// 校验if (amount.compareTo(new BigDecimal(1000)) >= 0){logger.info("贷款金额校验(限额1000元),单号:{} 金额:{}", orderId, amount);return "0001";}// 模拟生成贷款单logger.info("生成贷款单,单号:{} 金额:{}", orderId, amount);// 模拟支付成功logger.info("贷款成功,单号:{} 金额:{}", orderId, amount);return "0000";}@Overridepublic String recharge(String orderId, BigDecimal amount) {// 模拟生成还款单logger.info("生成还款单,单号:{} 金额:{}", orderId, amount);// 模拟还款成功logger.info("还款成功,单号:{} 金额:{}", orderId, amount);return "0000";}@Overridepublic List<String> tradeFlow() {return super.tradeFlow();}
}

信用卡的功能实现是在继承了储蓄卡类后,进行相关方法重写,这种继承父类方式的优点是复用了父类的核心功能逻辑,但是也破坏了原有的方法。此时继承父类实现的信用卡类并不满足里氏替换原则,也就是说,此时的子类不能承担原父类的功能,直接给储蓄卡使用。

里氏替换原则改善代码

储蓄卡和信用卡在功能使用上有些许类似,在实际的开发过程中也有很多共同可复用的属性及逻辑。实现这样的类的最好方式是提取出一个抽象类,由抽象类定义所有卡的共用核心属性、逻辑,把卡的支付和还款等动作抽象成正向和逆向操作

抽象银行卡类

public abstract class BankCard {private Logger logger = LoggerFactory.getLogger(BankCard.class);private String cardNo;   // 卡号private String cardDate; // 开卡时间public BankCard(String cardNo, String cardDate) {this.cardNo = cardNo;this.cardDate = cardDate;}abstract boolean rule(BigDecimal amount);// 正向入账,+ 钱public String positive(String orderId, BigDecimal amount) {// 入款成功,存款、还款logger.info("卡号{} 入款成功,单号:{} 金额:{}", cardNo, orderId, amount);return "0000";}// 逆向入账,- 钱public String negative(String orderId, BigDecimal amount) {// 入款成功,存款、还款logger.info("卡号{} 出款成功,单号:{} 金额:{}", cardNo, orderId, amount);return "0000";}/*** 交易流水查询** @return 交易流水*/public List<String> tradeFlow() {logger.info("交易流水查询成功");List<String> tradeList = new ArrayList<String>();tradeList.add("100001,100.00");tradeList.add("100001,80.00");tradeList.add("100001,76.50");tradeList.add("100001,126.00");return tradeList;}public String getCardNo() {return cardNo;}public String getCardDate() {return cardDate;}
}

在抽象银行卡类中,提供了基本的卡属性,包括卡号、开卡时间及三个核心方法。正向入账,加钱;逆向入账,减钱。接下来继承这个抽象类,实现储蓄卡的功能逻辑。

储蓄卡类实现

public class CashCard extends BankCard {private Logger logger = LoggerFactory.getLogger(CashCard.class);public CashCard(String cardNo, String cardDate) {super(cardNo, cardDate);}boolean rule(BigDecimal amount) {return true;}/*** 提现** @param orderId 单号* @param amount  金额* @return 状态码 0000成功、0001失败、0002重复*/public String withdrawal(String orderId, BigDecimal amount) {// 模拟支付成功logger.info("提现成功,单号:{} 金额:{}", orderId, amount);return super.negative(orderId, amount);}/*** 储蓄** @param orderId 单号* @param amount  金额*/public String recharge(String orderId, BigDecimal amount) {// 模拟充值成功logger.info("储蓄成功,单号:{} 金额:{}", orderId, amount);return super.positive(orderId, amount);}/*** 风险校验** @param cardNo  卡号* @param orderId 单号* @param amount  金额* @return 状态*/public boolean checkRisk(String cardNo, String orderId, BigDecimal amount) {// 模拟风控校验logger.info("风控校验,卡号:{} 单号:{} 金额:{}", cardNo, orderId, amount);return true;}}

储蓄卡类中继承抽象银行卡父类 BankCard,实现的核心功能包括规则过滤、提现、储蓄和新增的扩展方法,即风控校验。这样的实现方式满足了里氏替换的基本原则,既实现抽象类的抽象方法,又没有破坏父类中的原有方法。接下来实现信用卡的功能,信用卡的功能可以继承于储蓄卡,也可以继承抽象银行卡父类。

信用卡类实现

public class CreditCard extends CashCard {private Logger logger = LoggerFactory.getLogger(CreditCard.class);public CreditCard(String cardNo, String cardDate) {super(cardNo, cardDate);}boolean rule2(BigDecimal amount) {return amount.compareTo(new BigDecimal(1000)) <= 0;}/*** 提现,信用卡贷款** @param orderId 单号* @param amount  金额* @return 状态码*/public String loan(String orderId, BigDecimal amount) {boolean rule = rule2(amount);if (!rule) {logger.info("生成贷款单失败,金额超限。单号:{} 金额:{}", orderId, amount);return "0001";}// 模拟生成贷款单logger.info("生成贷款单,单号:{} 金额:{}", orderId, amount);// 模拟支付成功logger.info("贷款成功,单号:{} 金额:{}", orderId, amount);return super.negative(orderId, amount);}/*** 还款,信用卡还款** @param orderId 单号* @param amount  金额* @return 状态码*/public String repayment(String orderId, BigDecimal amount) {// 模拟生成还款单logger.info("生成还款单,单号:{} 金额:{}", orderId, amount);// 模拟还款成功logger.info("还款成功,单号:{} 金额:{}", orderId, amount);return super.positive(orderId, amount);}}

信用卡类在继承父类后,使用了公用的属性,即卡号、开卡时间,同时新增了符合信用卡功能的新方法,即贷款和还款,并在两个方法中都使用了抽象类的核心功能。另外新增了关于储蓄卡中的规则校验方法,并没有破坏储蓄卡中的校验方法。以上的实现方式都是在遵循里氏替换原则下完成的,子类随时可以替代储蓄卡类。

功能测试

储蓄卡

 信用卡

 信用卡替换储蓄卡

通过以上的测试结果可以看到,储蓄卡功能正常,继承储蓄卡实现的信用卡功能也正常。同时,原有储蓄卡类的功能可以由信用卡类支持。


继承的开发方式会给代码带来侵入性,可移植能力降低,类之间的耦合度较高。当对父类修改时,就要考虑一整套子类的实现是否有风险,测试成本较高。

里氏替换原则的目的是使用约定的方式,让使用继承后的代码具备良好的扩展性和兼容性。在日常开发中使用继承的地方并不多,在有些公司的代码规范中也不会允许多层继承,尤其是一些核心服务的扩展。而继承多数用在系统架构初期定义好的逻辑上或抽象出的核心功能里。如果使用了继承,就一定要遵从里氏替换原则,否则会让代码出现问题的概率变得更大。

内容来源 ---《重学Java设计模式》

Java设计模式(03) -- 里氏替换原则相关推荐

  1. Java设计模式之里氏替换原则(Liskov Substitution principle)

    2019独角兽企业重金招聘Python工程师标准>>> Java是面向对象的语言,那么什么是面向对象程序设计呢? 面向对象程序设计(英语:Object-oriented progra ...

  2. 北风设计模式课程---里氏替换原则(Liskov Substitution Principle)

    北风设计模式课程---里氏替换原则(Liskov Substitution Principle) 一.总结 一句话总结: 当衍生类能够完全替代它们的基类时:(Liskov Substitution P ...

  3. 设计模式之里氏替换原则示例

    设计模式之里氏替换原则示例 里氏替换原则强调的是设计和实现要依赖于抽象而非具体:子类只能去扩展基类,而不是隐藏或者覆盖基类,它包含4层含义. 一.里氏替换4原则 1.子类可以实现父类的抽象方法,但不能 ...

  4. 《设计模式》——里氏替换原则

    先扯两句 原本是不想扯了的,因为很久没扯了也不知道该说写什么,可是这里氏替换原则东西实在是太多了,我看过都快一周了,但是每次想写博客的时候,都写几个字就扔下了,倒不是说书中的内容不够详细,只是如果都是 ...

  5. 设计模式之里氏替换原则

    引入里氏替换原则(LiskovSubstitution Principle,LSP)的目的就是为了解决继承中父子类之间产生的强耦合性. 2.1 爱恨纠葛的父子关系 继承 在面向对象的语言中,继承是必不 ...

  6. 嘻哈说:设计模式之里氏替换原则

    1.定义 按照惯例,首先我们来看一下里氏替换原则的定义. 所有引用基类(父类)的地方必须能透明地使用其子类的对象. 通俗的说,子类可以扩展父类功能,但不能改变父类原有功能. 核心思想是继承. 通过继承 ...

  7. 【设计模式】里氏替换原则

    里氏替换原则 OO(Object Oriented,面向对象程式设计)中的继承性的思考和说明 继承包含这样一层含义∶父类中凡是已经实现好的方法,实际上是在设定规范和契约,虽然它不强制要求所有
的子类必 ...

  8. 设计模式:里氏替换原则

    里氏替换原则(Liskov Substitution Principle ,LSP): 指的是任何基类可以出现的地方,子类一定可以出现. 定义1 如果对每一个类型为T1的对象o1,都有类型为T2的对象 ...

  9. 设计模式之 里氏替换原则

    里氏替换原则(Liskov Substitution Principle, LSP):所有引用父类的地方必须能使用其子类的对象. 简单来说就是 子类拥有父类所拥有的一切,并可以在此基础上进而扩展其他的 ...

最新文章

  1. 波士顿动力机器狗新技能!跳绳园艺做家务,还有书法神技
  2. Android的第一个工程,Android Things:撸起袖子来创建第一个Things工程
  3. 我国无线2.4g及5g信道-个人笔记
  4. 数据表对应关系(一对一、一对多、多对多)
  5. Istio 1.4 部署指南
  6. 大鱼吃小鱼java源代码_大鱼吃小鱼 JAVA游戏源码
  7. IIS7.0 CSS、JS、图片报500错误
  8. JavaScript 行间事件、提取行间事件
  9. 沉痛悼念“中国航天之父”、我校创始人之一、近代力学系首任系主任钱学森先生
  10. omapl138移植uboot系列之在线升级(第八篇完结篇)
  11. layUi upload单文件上传,重复上传的问题
  12. 解决google play三件套无法连接到互联网、保护机制认证、商店无法进入等常见问题
  13. 开关电源模块并联供电系统_水冷风冷高频开关电源直流整流器
  14. Kubernetes K8S之通过helm部署metrics-server与HPA详解
  15. 使用Canvas 实现手机端签名
  16. mSystems:华南农大揭示水稻SST基因调控根际微生物协同耐盐机制
  17. 如何在本地搭建一个EasyPlayer的H5 demo 播放H265视频流?
  18. php 数字货币格式化,NPM酷库:accounting,格式化数字和货币
  19. 我是做地推的,一个月收入大概2W左右
  20. “子弹短信也压根撼动不了腾讯” | 畅言

热门文章

  1. 以太网采用的拓扑结构基本是什么型
  2. 安卓巴士总结了近百个Android优秀开源项
  3. Unity3D接入第三方插件之微信登录安卓SDK
  4. matlab hdf5,无法使用最新的HDF5打开matlab文件
  5. Robocup 仿真2D 学习笔记(三)最初的改进1
  6. 笔记本WIFI时断时续(已解决)2021
  7. 股票6题(Aced)
  8. 怎么定位html的坐标,css怎么定位图片的位置?
  9. 2018, 数据分析师的就业前景如何?
  10. 树莓派连接隐藏wifi(2020最新系统实测可用!)