焦作公司做网站,网站页面设计图片素材,自己编写的网站如何放到wordpress,网站建设合同定义文章目录 一、定义#xff1a;里氏替换原则1.1 里氏替换原则1.2 里氏替换原则的作用 二、模拟场景#xff1a;里氏替换原则三、违背方案#xff1a;里氏替换原则3.1 工程结构3.2 储蓄卡和信用卡3.2.1 储蓄卡3.2.2 信用卡 3.3 单元测试3.3.1 储蓄卡测试3.3.2 信用卡测试 四、… 文章目录 一、定义里氏替换原则1.1 里氏替换原则1.2 里氏替换原则的作用 二、模拟场景里氏替换原则三、违背方案里氏替换原则3.1 工程结构3.2 储蓄卡和信用卡3.2.1 储蓄卡3.2.2 信用卡 3.3 单元测试3.3.1 储蓄卡测试3.3.2 信用卡测试 四、改善代码里氏替换原则4.1 工程结构4.2 银行卡储蓄卡和信用卡4.2.1 抽象银行卡类4.2.2 储蓄卡4.2.2 信用卡 4.3 单元测试4.3.1 里氏替换测试4.3.2 信用卡测试 五、总结里氏替换原则 一、定义里氏替换原则
1.1 里氏替换原则
里氏替换原则Liskov Substitution PrincipleLSP。 如果 S 是 T 的子类型那么所有 T 类型的对象都可以在不破坏程序的情况下被 S 类型的对象替换。简单来说子类可以扩展父类的功能但不能改变父类原有的功能。也就是说当子类继承父类时除添加新的方法且完成新增功能外尽量不要重写父类的方法。 子类可以实现父类的抽象方法但不能覆盖父类的非抽象方法。子类可以增加自己特有的方法。当子类的方法重载父类的方法时方法的前置条件即方法的输入参数要比父类的方法更宽松。当子类的方法实现父类的方法重写、重载或实现抽象方法时方法的后置条件即方法的输出或返回值要比父类的方法更严格或与父类的方法相等。
1.2 里氏替换原则的作用
里氏替换原则是实现开闭原则的重要方式之一。解决了继承中重写父类造成的可复用性变差的问题。是动作正确性的保证即类的扩展不会给已有的系统引入新的错误降低了代码出错的可能性。加强程序的健壮性同时变更时可以做到非常好的兼容性提高程序的维护性、可扩展性降低需求变更时引入的风险。
二、模拟场景里氏替换原则
我们会使用各种类型的银行卡例如储蓄卡、信用卡还有一些其他特性的银行卡。 储蓄卡和信用卡都具备一定的消费功能但又有一些不同。例如信用卡不宜提现如果提现可能会产生高额的利息。 模拟场景假设在构建银行系统时储蓄卡是第一个类信用卡是第二个类。为了让信用卡可以使用储蓄卡的一些方法选择由信用卡类继承储蓄卡类讨论是否满足里氏替换原则产生的一些要点。
三、违背方案里氏替换原则
储蓄卡和信用卡在使用功能上类似都有支付、提现、还款、充值等功能但有些许不同。 例如支付储蓄卡做的是账户扣款动作信用卡做的是生成贷款单动作。
3.1 工程结构
design-1.3-0
|——src|——main|--java|--com.lino.design|--CashCard.java|--CreditCard.java|——test|--java|--com.lino.design.test|--ApiTest.java3.2 储蓄卡和信用卡
3.2.1 储蓄卡 CashCard.java package com.lino.design;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;/*** description: 模拟储蓄卡功能*/
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 金额* return 状态码 0000成功、0001失败、0002重复*/public String recharge(String orderId, BigDecimal amount) {// 模拟充值成功logger.info(储值成功单号{} 金额{}, orderId, amount);return 0000;}/*** 交易流水查询** return 交易流水*/public ListString tradeFlow() {logger.info(交易流水查询成功);ListString tradeList new ArrayList();tradeList.add(100001,100.00);tradeList.add(100001,80.00);tradeList.add(100001,76.50);tradeList.add(100001,126.00);return tradeList;}
}在储蓄卡的功能实现中包括了三个方法提现、储蓄、交易流水查询这些是模拟储蓄卡的基本功能。
3.2.2 信用卡 CreditCard.java package com.lino.design;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.List;/*** description: 模拟信用卡功能*/
public class CreditCard extends CashCard {private Logger logger LoggerFactory.getLogger(CreditCard.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 ListString tradeFlow() {return super.tradeFlow();}}信用卡的功能实现是在继承了储蓄卡类后进行方法重学支付 withdrawal()、还款 recharge()。交易流水可以复用不用重写这个类。这种继承父类方式的优点是复用了父类的核心功能逻辑但是也破坏了原有的方法。 此时继承父类实现的信用卡类并不满足里氏替换原则也就是说此时的子类不能承担原父类的功能直接给储蓄卡使用。
3.3 单元测试
3.3.1 储蓄卡测试 ApiTest.java Test
public void test_CashCard() {CashCard cashCard new CashCard();// 提现cashCard.withdrawal(100001, new BigDecimal(100));// 储蓄cashCard.recharge(100001, new BigDecimal(100));// 交易流水ListString tradeFlow cashCard.tradeFlow();logger.info(查询交易流水{}, JSON.toJSONString(tradeFlow));
}测试结果 10:58:28.027 [main] INFO com.lino.design.CashCard - 提现成功单号100001 金额100
10:58:28.031 [main] INFO com.lino.design.CashCard - 储值成功单号100001 金额100
10:58:28.031 [main] INFO com.lino.design.CashCard - 交易流水查询成功
10:58:28.169 [main] INFO com.lino.design.test.ApiTest - 查询交易流水[100001,100.00,100001,80.00,100001,76.50,100001,126.00]3.3.2 信用卡测试 ApiTest.java Test
public void test_CreditCard() {CreditCard creditCard new CreditCard();// 支付creditCard.withdrawal(100001, new BigDecimal(100));// 还款creditCard.recharge(100001, new BigDecimal(100));// 交易流水ListString tradeFlow creditCard.tradeFlow();logger.info(查询交易流水{}, JSON.toJSONString(tradeFlow));
}测试结果 10:59:23.970 [main] INFO com.lino.design.CreditCard - 生成贷款单单号100001 金额100
10:59:23.970 [main] INFO com.lino.design.CreditCard - 贷款成功单号100001 金额100
10:59:23.970 [main] INFO com.lino.design.CreditCard - 生成还款单单号100001 金额100
10:59:23.970 [main] INFO com.lino.design.CreditCard - 还款成功单号100001 金额100
10:59:23.970 [main] INFO com.lino.design.CashCard - 交易流水查询成功
10:59:24.003 [main] INFO com.lino.design.test.ApiTest - 查询交易流水[100001,100.00,100001,80.00,100001,76.50,100001,126.00]四、改善代码里氏替换原则
4.1 工程结构
design-1.3-1
|——src|——main|--java|--com.lino.design|--BandCard.java|--CashCard.java|--CreditCard.java|——test|--java|--com.lino.design.test|--ApiTest.java4.2 银行卡储蓄卡和信用卡
储蓄卡和信用卡在功能使用上有些许类似在实际的开发过程中也有很多共同可复用的属性及逻辑。实现这样的类的最好方式是提取出一个抽象类由抽象类定义所有卡的共用核心属性、逻辑把卡的支付和还款等动作抽象成正向和逆向操作。
4.2.1 抽象银行卡类 BandCard.java package com.lino.design;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;/*** description: 银行卡*/
public abstract class BandCard {private Logger logger LoggerFactory.getLogger(BandCard.class);/*** 卡号*/private String cardNo;/*** 开卡时间*/private String cardDate;public BandCard(String cardNo, String cardDate) {this.cardNo cardNo;this.cardDate cardDate;}/*** 金额判断规则** param amount 金额* return 是否符合规则*/abstract boolean rule(BigDecimal amount);/*** 正向入账钱** param orderId 单号* param amount 金额* return 状态码*/public String positive(String orderId, BigDecimal amount) {// 入款成功存款、还款logger.info(卡号{} 入款成功单号{} 金额{}, cardNo, orderId, amount);return 0000;}/*** 逆向入账-钱** param orderId 单号* param amount 金额* return 状态码*/public String negative(String orderId, BigDecimal amount) {// 出款成功支付、贷款logger.info(卡号{} 出款成功单号{} 金额{}, cardNo, orderId, amount);return 0000;}/*** 交易流水查询** return 交易流水*/public ListString tradeFlow() {logger.info(交易流水查询成功);ListString tradeList new ArrayList();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;}
}在抽象银行卡类中提供了基本的卡属性包括卡号、开卡时间及三个核心方法。 正向入账加钱逆向入账减钱交易流水查询
4.2.2 储蓄卡 CashCard.java package com.lino.design;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;/*** description: 模拟储值卡功能*/
public class CashCard extends BandCard {private Logger logger LoggerFactory.getLogger(CashCard.class);public CashCard(String cardNo, String cardDate) {super(cardNo, cardDate);}Overrideboolean 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 金额* return 状态码 0000成功、0001失败、0002重复*/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;}
}储蓄卡类中继承抽象银行卡父类 BandCard实现的核心功能包括规则过滤 rule、提现 withdrawal、储蓄 recharge 和新增的扩展方法即风控校验 checkRisk。这样的实现方式满足了里氏替换的基本原则即实现抽象类的抽象方法又没有破坏父类中的原有方法。
4.2.2 信用卡 CreditCard.java package com.lino.design;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;/*** description: 信用卡*/
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);}
}信用卡类在继承父类后使用了公用的属性即卡号 cardNo、开卡时间 cardDate同时新增了符合信用卡功能的新方法即贷款 loan、还款 repayment并在两个方法中都使用了抽象类的核心功能。另外关于储蓄卡中的规则校验方法新增了自己的规则方法 rule2并没有破坏储蓄卡中的校验方法。以上的实现方式都是在遵循里氏替换原则下完成的子类随时可以替换储蓄卡类。
4.3 单元测试
4.3.1 里氏替换测试 ApiTest.java Test
public void test_bandCard() {logger.info(里氏替换前CashCard类);CashCard bandCard new CashCard(6214567800989876, 2022-12-14);// 提现bandCard.withdrawal(100001, new BigDecimal(100));// 储蓄bandCard.recharge(100001, new BigDecimal(100));logger.info(里氏替换后CreditCard类);CashCard creditCard new CreditCard(6214567800989876, 2022-12-14);// 提现creditCard.withdrawal(100001, new BigDecimal(1000000));// 储蓄creditCard.recharge(100001, new BigDecimal(100));
}测试结果 11:16:03.817 [main] INFO com.lino.design.test.ApiTest - 里氏替换前CashCard类
11:16:03.817 [main] INFO com.lino.design.CashCard - 提现成功单号100001 金额100
11:16:03.817 [main] INFO com.lino.design.BandCard - 卡号6214567800989876 出款成功单号100001 金额100
11:16:03.817 [main] INFO com.lino.design.CashCard - 储值成功单号100001 金额100
11:16:03.817 [main] INFO com.lino.design.BandCard - 卡号6214567800989876 入款成功单号100001 金额100
11:16:03.817 [main] INFO com.lino.design.test.ApiTest - 里氏替换后CreditCard类
11:16:03.817 [main] INFO com.lino.design.CashCard - 提现成功单号100001 金额1000000
11:16:03.817 [main] INFO com.lino.design.BandCard - 卡号6214567800989876 出款成功单号100001 金额1000000
11:16:03.817 [main] INFO com.lino.design.CashCard - 储值成功单号100001 金额100
11:16:03.817 [main] INFO com.lino.design.BandCard - 卡号6214567800989876 入款成功单号100001 金额1004.3.2 信用卡测试 ApiTest.java Test
public void test_CreditCard() {CreditCard creditCard new CreditCard(6214567800989876, 2022-12-14);// 支付贷款creditCard.loan(100001, new BigDecimal(100));// 还款creditCard.repayment(100001, new BigDecimal(100));
}测试结果 11:13:03.042 [main] INFO com.lino.design.CreditCard - 生成贷款单单号100001 金额100
11:13:03.042 [main] INFO com.lino.design.CreditCard - 贷款成功单号100001 金额100
11:13:03.042 [main] INFO com.lino.design.BandCard - 卡号6214567800989876 出款成功单号100001 金额100
11:13:03.042 [main] INFO com.lino.design.CreditCard - 生成还款单单号100001 金额100
11:13:03.042 [main] INFO com.lino.design.CreditCard - 还款成功单号100001 金额100
11:13:03.042 [main] INFO com.lino.design.BandCard - 卡号6214567800989876 入款成功单号100001 金额100通过以上的测试结果可以看到储蓄卡功能正常继承储蓄卡实现的信用卡功能也正常。同时原有储蓄卡类的功能可以由信用卡类支持即 CashCard creditCard new CreditCard()。
五、总结里氏替换原则
继承作为面向对象的重要特征虽然给程序开发带来了非常大的便利但也引入了一些弊端。 继承的开发方式会给代码带来侵入性可移植能力降低类之间的耦合度较高。当对父类修改时就要考虑一整套子类的实现是否由风险测试成本较高。 里氏替换原则的目的是使用约定的方式让使用继承后的代码具备良好的扩展性和兼容性。 在日常开发中使用继承的地方并不多在代码规范中也不会允许多层继承尤其是一些核心服务的扩展。而继承多数用在系统架构初期定义好的逻辑上或抽象出的核心功能里。如果使用了继承就一定要遵从里氏替换原则否则会让代码出现问题的概率变得更大。