设计模式--设计原则--里氏替换原则

71 阅读8分钟

定义

里氏替换原则(Liskov Substitution Principle, LSP)于1986年有Barbara Liskov提出,他当时是这样描述这条原则的:如果S是T的子类型,那么T的对象可以被S的对象所替换,并不影响代码的运行。1996年,Robert Martin在他的SOLID原则中重新描述了里氏替换原则:使用父类对象的函数可以在不了解子类的情况下替换为使用子类对象。 结合上面的描述,我们将里氏替换原则描述为:子类对象能够替换到程序中的父类对象出现的任何地方,并且保证程序原有的逻辑行为不变和正确性不被破坏

模拟案例

假设在构建银行系统时,储蓄卡是第一个类,信用卡是第二个类。为了让信用卡复用储蓄卡的一些方法,由信用卡继承储蓄卡。 储蓄卡:提现、充值,相关支付功能是直接账户扣款 信用卡:提现、充值,相关支付功能是生成账单的动作。

违背原则实现

1.储蓄卡类

/**
 * @description:模拟储蓄卡
 * @version: 1.0
 * @Author blackcat
 */
@Slf4j
public class CashCard {
​
    /**
     * 提现
     *
     * @param orderId 单号
     * @param amount  金额
     * @return 状态码 0000成功、0001失败、0002重复
     */
    public String withdrawal(String orderId, BigDecimal amount) {
        // 模拟账户支付成功
        log.info("提现成功,单号:{} 金额:{}", orderId, amount);
        return "0000";
    }
​
    /**
     * 储蓄
     *
     * @param orderId 单号
     * @param amount  金额
     */
    public String recharge(String orderId, BigDecimal amount) {
        // 模拟账户充值成功
        log.info("储蓄成功,单号:{} 金额:{}", orderId, amount);
        return "0000";
    }
​
    /**
     * 交易流水查询
     * @return 交易流水
     */
    public List<String> tradeFlow() {
        log.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;
    }
}
​

2.信用卡类

/**
 * @description:模拟信用卡
 * @version: 1.0
 * @Author blackcat
 */
@Slf4j
public class CreditCard extends CashCard {
​
    @Override
    public String withdrawal(String orderId, BigDecimal amount) {
        // 校验
        if (amount.compareTo(new BigDecimal(1000)) >= 0) {
            log.info("贷款金额校验(限额1000元),单号:{} 金额:{}", orderId, amount);
            return "0001";
        }
        // 模拟生成贷款单
        log.info("生成贷款单,单号:{} 金额:{}", orderId, amount);
        // 模拟支付成功
        log.info("贷款成功,单号:{} 金额:{}", orderId, amount);
        return "0000";
    }
​
    @Override
    public String recharge(String orderId, BigDecimal amount) {
        // 模拟生成还款单
        log.info("生成还款单,单号:{} 金额:{}", orderId, amount);
        // 模拟还款成功
        log.info("还款成功,单号:{} 金额:{}", orderId, amount);
        return "0000";
    }
}
​

3.测试

/**
 * @description:
 * @version: 1.0
 * @Author blackcat
 */
@Slf4j
public class ApiTest {
​
    @Test
    public void testCashCard() {
        CashCard cashCard = new CashCard();
        // 提现
        cashCard.withdrawal("100001", new BigDecimal(100));
        // 储蓄
        cashCard.recharge("100001", new BigDecimal(100));
        // 交易流水
        List<String> tradeFlow = cashCard.tradeFlow();
        log.info("查询交易流水,{}", Arrays.toString(tradeFlow.toArray()));
    }
​
    @Test
    public void testCreditCard() {
        CreditCard creditCard = new CreditCard();
        // 支付
        creditCard.withdrawal("100001", new BigDecimal(100));
        // 还款
        creditCard.recharge("100001", new BigDecimal(100));
        // 交易流水
        List<String> tradeFlow = creditCard.tradeFlow();
        log.info("查询交易流水,{}", Arrays.toString(tradeFlow.toArray()));
    }
​
​
}
​

[main] INFO com.black.cat.design.step01.lsp.CashCard - 提现成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.CashCard - 储蓄成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.CashCard - 交易流水查询成功 [main] INFO com.black.cat.design.step01.lsp.ApiTest - 查询交易流水,[100001,100.00, 100001,80.00, 100001,76.50, 100001,126.00]

[main] INFO com.black.cat.design.step01.lsp.CreditCard - 生成贷款单,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.CreditCard - 贷款成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.CreditCard - 生成还款单,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.CreditCard - 还款成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.CashCard - 交易流水查询成功 [main] INFO com.black.cat.design.step01.lsp.ApiTest - 查询交易流水,[100001,100.00, 100001,80.00, 100001,76.50, 100001,126.00]

子类虽然复用了父类的功能,但是破坏了原有的方法,不满足里氏替换原则,子类不能承担父类原有的功能。

里氏替换优化

1.抽象银行卡类

/**
 * @description:抽象银行卡类
 * @version: 1.0
 * @Author blackcat
 */
@Slf4j
public class BankCard {
​
    /**
     * 卡号
     */
    private String cardNo;
​
    /**
     * 开卡时间
     */
    private String cardDate;
​
    public BankCard(String cardNo, String cardDate) {
        this.cardNo = cardNo;
        this.cardDate = cardDate;
    }
​
    /**
     * 正向入账,加钱
     * @param orderId
     * @param amount
     * @return
     */
    public String positive(String orderId, BigDecimal amount) {
        // 入款成功,存款、还款
        log.info("卡号{} 入款成功,单号:{} 金额:{}", cardNo, orderId, amount);
        return "0000";
    }
​
    /**
     * 逆向入账,减钱
     * @param orderId
     * @param amount
     * @return
     */
    public String negative(String orderId, BigDecimal amount) {
        // 入款成功,存款、还款
        log.info("卡号{} 出款成功,单号:{} 金额:{}", cardNo, orderId, amount);
        return "0000";
    }
​
​
    /**
     * 交易流水查询
     *
     * @return 交易流水
     */
    public List<String> tradeFlow() {
        log.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;
    }
}
​

2.储蓄卡类

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

3.银行卡类

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

测试

@Slf4j
public class ApiTest {
​
​
    @Test
    public void test_bankCard() {
        log.info("里氏替换前,CashCard类:");
        CashCard bankCard = new CashCard("6214567800989876", "2022-03-05");
        // 提现
        bankCard.withdrawal("100001", new BigDecimal(100));
        // 储蓄
        bankCard.recharge("100001", new BigDecimal(100));
​
        log.info("里氏替换后,CreditCard类:");
        CashCard creditCard = new CreditCard("6214567800989876", "2022-03-05");
        // 提现
        creditCard.withdrawal("100001", new BigDecimal(1000000));
        // 储蓄
        creditCard.recharge("100001", new BigDecimal(100));
    }
​
    @Test
    public void test_creditCard(){
        CreditCard creditCard = new CreditCard("6214567800989876", "2022-03-05");
        // 支付,贷款
        creditCard.loan("100001", new BigDecimal(100));
        // 还款
        creditCard.repayment("100001", new BigDecimal(100));
    }
​
}
​

[main] INFO com.black.cat.design.step01.lsp.review.ApiTest - 里氏替换前,CashCard类: [main] INFO com.black.cat.design.step01.lsp.review.CashCard - 提现成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.review.BankCard - 卡号6214567800989876 出款成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.review.CashCard - 储蓄成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.review.BankCard - 卡号6214567800989876 入款成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.review.ApiTest - 里氏替换后,CreditCard类: [main] INFO com.black.cat.design.step01.lsp.review.CashCard - 提现成功,单号:100001 金额:1000000 [main] INFO com.black.cat.design.step01.lsp.review.BankCard - 卡号6214567800989876 出款成功,单号:100001 金额:1000000 22:16:54.642 [main] INFO com.black.cat.design.step01.lsp.review.CashCard - 储蓄成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.review.BankCard - 卡号6214567800989876 入款成功,单号:100001 金额:100

[main] INFO com.black.cat.design.step01.lsp.review.CreditCard - 生成贷款单,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.review.CreditCard - 贷款成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.review.BankCard - 卡号6214567800989876 出款成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.review.CreditCard - 生成还款单,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.review.CreditCard - 还款成功,单号:100001 金额:100 [main] INFO com.black.cat.design.step01.lsp.review.BankCard - 卡号6214567800989876 入款成功,单号:100001 金额:100

关键如何定义抽象类以及类之间的公共方法。 体现和储蓄的功能不同的卡命名为不同的方法,抽取公共的调用方法。

总结

里氏替换原则是对「开闭原则」的补充,实现「开闭原则」的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范;

简单理解就是子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法,如果子类强制要重写父类的方法,那么可以再抽象一个基类,为他们的公共父类,或采用依赖、组合、聚合的方式来实现;