六大设计原则之里氏替换原则04

目录

1.概述

2.里氏替换原则的作用

3.业务场景

4.运用设计原则前代码实现

4.1.代码实现

4.2.总结

5.运用设计原则后代码实现

5.1.代码实现

5.2.总结


1.概述

里氏替换原则(Liskov Substitution Principle,LSP), 由麻省理工学院计算机科学系教授芭芭拉.利斯科夫(barbara Liskov)于1987年在"面向对象技术的高峰会议"上发表的一篇文章《数据抽象和层次》(Data Abstraction and Hierarchy)里提出的,她提出:继承必须确保超类所拥有的性质在子类中仍然成立。

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

1).子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。

2).子类可以增加自己特有的方法。

3).当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松。

4).当子类的方法实现父类的方法(重写,重载或实现抽象方法)时,方法的后置条件(即方法的输出或返回值)要比父类的方法更严格或与父类的方法相等。

2.里氏替换原则的作用

1).里氏替换原则是实现开闭原则的重要方式之一。

2).解决了继承中重写父类造成的可复用性变差的问题。

3).是动作正确性的保证,即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。

4).加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性,可扩展性,降低需求变更时引入的风险。

3.业务场景

关于里氏替换的场景,最有名的就是“正方形不是长方形”。这里选择不同种类的银行卡作为场景进行学习,我们会使用各种类型的银行卡,例如储蓄卡,信用卡,还有一些其他特性的银行卡。储蓄卡和信用卡都具备一定的消费功能,但又有一些不同。例如信用卡不宜提现,如果提现可能会产生高额利息,下面构建这样一个模拟场景,假设在构建银行系统时,储蓄卡是第一个类,信用卡是第二个类。为了让信用卡可以使用储蓄卡的一些方法,选择由信用卡类继承储蓄卡类,讨论是否满足里氏替换原则产生的一些要点。

4.运用设计原则前代码实现

4.1.代码实现

CashCard.java

package chapter02.lsp;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
​
/**
 * 模拟储蓄卡功能
 */
public class CashCard {
    private Logger logger = LoggerFactory.getLogger(CashCard.class);
​
    /**
     * 提现
     *
     * @param orderId 单号
     * @param amount  金额
     * @return 状态码 0000 成功, 0001失败,0002 重复
     */
    public String withdrawl(String orderId, BigDecimal amount) {
        //模拟支付成功
        logger.info("提现成功,单号:{} 金额:{}", orderId, amount);
        return "0000";
    }
​
    /**
     * 存储
     *
     * @param orderId 单号
     * @param amount  金额
     * @return
     */
    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.0");
        tradeList.add("100001,80.0");
        tradeList.add("100001,76.5");
        tradeList.add("100001,126.00");
        return tradeList;
    }
}

CreditCard.java

package chapter02.lsp;
​
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.util.List;
​
/**
 * 模拟信用卡功能
 */
public class CreditCard extends CashCard {
    private Logger logger = LoggerFactory.getLogger(CreditCard.class);
​
    public String withdrawl(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";
    }
​
    public String recharge(String orderId, BigDecimal amount) {
        //模拟生成还款单
        logger.info("生成还款单,单号:{},金额:{}", orderId, amount);
        //模拟还款成功
        logger.info("还款成功,单号:{},金额:{}", orderId, amount);
        return "0000";
    }
​
    public List<String> tradeFlow() {
        return super.tradeFlow();
    }
}

4.2.总结

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

5.运用设计原则后代码实现

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

5.1.代码实现

抽象类 BankCard.java

package chapter02.lsp;
​
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
​import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
​
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";
    }
​
    public List<String> tradeFlow(){
        logger.info("交易流水查询成功!");
        List<String> tradeList = new ArrayList<String>();
        tradeList.add("100001,100.0");
        tradeList.add("100001,80.0");
        tradeList.add("100001,76.5");
        tradeList.add("100001,126.00");
        return tradeList;
    }
​
}

储蓄卡类实现 CashCardExt.java

package chapter02.lsp;
​
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
​
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
​
/**
 * 模拟储蓄卡功能
 */
public class CashCardExt extends BankCard {
    private Logger logger = LoggerFactory.getLogger(CashCardExt.class);
​
    public CashCardExt(String cardNo, String cardDate) {
        super(cardNo, cardDate);
    }
​
    /**
     * 规则过滤,储蓄卡直接默认通过
     *
     * @param amount
     * @return
     */
    @Override
    boolean rule(BigDecimal amount) {
        return true;
    }
​
    /**
     * 提现
     *
     * @param orderId 单号
     * @param amount  金额
     * @return 状态码 0000 成功, 0001失败,0002 重复
     */
    public String withdrawl(String orderId, BigDecimal amount) {
        //模拟支付成功
        logger.info("提现成功,单号:{} 金额:{}", orderId, amount);
        return super.negative(orderId,amount);
    }
​
    /**
     * 存储
     *
     * @param orderId 单号
     * @param amount  金额
     * @return
     */
    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;
    }
​
    /**
     * 交易流水查询
     *
     * @return 交易流水
     */
    public List<String> tradeFlow() {
        logger.info("交易流水查询成功!");
        List<String> tradeList = new ArrayList<String>();
        tradeList.add("100001,100.0");
        tradeList.add("100001,80.0");
        tradeList.add("100001,76.5");
        tradeList.add("100001,126.00");
        return tradeList;
    }
}
​

信用卡类实现 CreditCardExt.java

package chapter02.lsp;
​
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
​
import java.math.BigDecimal;
​
/**
 * 模拟信用卡功能
 */
public class CreditCardExt extends CashCardExt {
    private Logger logger = LoggerFactory.getLogger(CreditCardExt.class);
​
    public CreditCardExt(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);
    }
}

功能测试

package test.chapter02;
​
import chapter02.lsp.CashCardExt;
import chapter02.lsp.CreditCardExt;
import org.junit.Test;
​
import java.math.BigDecimal;
​
public class TestLsp {
​
    //功能测试:测试信用卡
    @Test
    public void testCashCard() {
        CashCardExt bankCard = new CashCardExt("6214567800989876", "2020-10-01");
        //提现
        bankCard.withdrawl("100001", new BigDecimal(100));
        //储蓄
        bankCard.recharge("100001", new BigDecimal(100));
    }
​
    //功能测试:信用卡
    @Test
    public void testCreditCard() {
        CreditCardExt creditCardExt = new CreditCardExt("6214567800989876", "2020-10-01");
        //支付,贷款
        creditCardExt.loan("10001", new BigDecimal(1000000));
        //还款
        creditCardExt.repayment("100001", new BigDecimal(1000000));
    }
​
    //功能测试:信用卡替换储蓄卡
    @Test
    public void testCreditCardToCashCard() {
        CashCardExt creditCard = new CreditCardExt("6214567800989876", "2020-10-01");
        //提现
        creditCard.withdrawl("100001", new BigDecimal(1000000));
        //储蓄
        creditCard.recharge("100001", new BigDecimal(100));
    }
}

5.2.总结

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

2.继承作为面向对象的重要特征,虽然给程序开发带来了非常大的便利,但也引入了一些弊端。继承的开发方式会给代码带来侵入性,可移植能力降低,类之间的耦合度较高。当对父类修改时,就要考虑一整套子类的实现是否有风险,测试成本较高。

3.里氏替换原则的目的是使用约定的方式,让使用继承后的代码具备良好的扩展性和兼容性,如果使用了继承,就一定要遵从里氏替换原则,否则会让代码出现问题的概率变得更大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值