【AI时代的设计模式:LSP原则的智能应用】

](https://img-home.csdnimg.cn/images/20220524100510.png#pic_center)
🌈个人主页: Aileen_0v0
🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法
💫个人格言:“没有罗马,那就自己创造罗马~”

前言

  • 本文主要讲解lsp原则 不是你以为的下面这种lsp, 🐶狗头保命)

在这里插入图片描述

在这里插入图片描述

lsp原则介绍

  • 里氏替换原则(LSP),这是SOLID原则之一。该原则由美国女计算机科学家里氏提出,强调在面向对象编程中,子类 可以替换父类的行为,但必须保持一致性。 具体来说,子类的操作结果必须与直接调用父类的操作结果一致。违反这一
    原则可能导致不可预期的行为,增加代码复杂度和出错概率。

    • 下面我们将以银行卡中的信用卡和储蓄卡为例,介绍不遵循LSP原则的例子,以及如何通过LSP进行优化。

不遵循里氏替换原则(LSP)的例子

我们可以通过以下情境来展示如何违反里氏替换原则:

  • 假设我们把 信用卡储蓄卡 都直接继承自 SavingsCard(而没有一个共同的 BankCard 基类)。在这种情况下,信用卡 会有一个额外的透支功能,而 储蓄卡 则没有透支功能。这样,如果一个方法仅依赖于储蓄卡的行为(即不能透支),但我们传入了信用卡对象,程序的行为就会不符合预期,导致错误或逻辑问题。

在这里插入图片描述

代码实现:不遵循里氏替换原则

储蓄卡

package principles.lsp;
public class SavingsCard {
    protected String cardNumber;  // 卡号
    protected double balance;     // 余额

    // 构造函数
    public SavingsCard(String cardNumber, double balance) {
        this.cardNumber = cardNumber;
        this.balance = balance;
    }

    // 存款方法
    public void deposit(double amount) {
        if (amount <= 0) {
            System.out.println("存款金额必须大于零!");
            return;
        }
        balance += amount;
        System.out.println("存款成功!当前余额:" + balance + "元");
    }

    // 取款方法
    public void withdraw(double amount) {
        if (amount <= 0) {
            System.out.println("取款金额必须大于零!");
            return;
        }
        if (amount > balance) {
            System.out.println("余额不足,取款失败!");
            return;
        }
        balance -= amount;
        System.out.println("取款成功!当前余额:" + balance + "元");
    }

    // 查询余额
    public void checkBalance() {
        System.out.println("当前余额:" + balance + "元");
    }
}

信用卡

package principles.lsp;

class CreditCard extends SavingsCard {
    private double creditLimit;  // 信用额度

    // 构造函数
    public CreditCard(String cardNumber, double balance, double creditLimit) {
        super(cardNumber, balance);
        this.creditLimit = creditLimit;
    }

    // 重写取款方法,允许透支
    @Override
    public void withdraw(double amount) {
        if (amount <= 0) {
            System.out.println("取款金额必须大于零!");
            return;
        }
        if (amount > balance + creditLimit) {
            System.out.println("余额不足,无法超过信用额度 " + creditLimit + " 取款!");
            return;
        }
        balance -= amount;
        System.out.println("取款成功!当前余额:" + balance + "元(信用额度剩余:" + (creditLimit + balance) + "元)");
    }
}

测试类

package principles.lsp;

// 测试类
public class BankTest {
    public static void main(String[] args) {
        // 创建储蓄卡和信用卡对象
        SavingsCard savingsCard = new SavingsCard("123456789", 1500);
        CreditCard creditCard = new CreditCard("987654321", 1500, 3000);

        // 储蓄卡操作
        System.out.println("-------- 储蓄卡操作 --------");
        savingsCard.deposit(500);    // 存款
        savingsCard.withdraw(1200);  // 取款
        savingsCard.checkBalance();  // 查询余额

        // 信用卡操作
        System.out.println("\n-------- 信用卡操作 --------");
        creditCard.deposit(500);    // 存款
        creditCard.withdraw(4500);  // 取款(透支)
        creditCard.checkBalance();  // 查询余额

        // 假设在某个地方,我们需要处理储蓄卡和信用卡,但只考虑了储蓄卡行为:
        System.out.println("\n-------- 处理储蓄卡的取款 --------");
        processWithdrawal(savingsCard, 1000);  // 传入储蓄卡

        System.out.println("\n-------- 处理信用卡的取款 --------");
        processWithdrawal(creditCard, 500);  // 传入信用卡
    }

    // 处理取款的方法,只考虑储蓄卡的行为
    public static void processWithdrawal(SavingsCard card, double amount) {
        System.out.println("===================");
        System.out.println("处理取款:");
        card.withdraw(amount);  // 只期望储蓄卡的行为(没有透支功能)
    }
}

运行结果及分析

在这里插入图片描述

在这个例子中,我们将 信用卡 直接继承 储蓄卡,并且重写了父类储蓄卡withdraw() 方法的行为,导致不同的类(储蓄卡和信用卡)有了不同的行为契约,破坏了里氏替换原则(当子类信用卡作为储蓄卡时去替换它的取款行为变成可透支的了,这使得子类和父类的操作结果不一致,因为储蓄卡是不可透支的,这就不符合储蓄卡本应有的行为)。

  • 因此,解决方案是确保在设计时遵循统一的行为契约,并抽象出合适的基类(如 BankCard),避免在子类中改变父类行为的预期。这才能保证子类在父类的替换场景中保持一致性,确保程序的正确性。
  • 👇️下面我将遵循lsp原则,将信用卡和储蓄卡的共性抽取出来,都继承自同一个父类BanCard,使得在程序中可以 使用父类 BankCard 类型 来引用它们,而程序行为仍然是正确的。

遵循lsp的新设计

替换:

  • 信用卡withdraw 方法和储蓄卡withdraw 方法是不同的。储蓄卡只能在余额范围内取款,而信用卡则可以透支。
  • 虽然这两个类在具体行为上有所不同,但它们都继承自同一个父类 BankCard。这使得在程序中可以 使用父类 BankCard 类型 来引用它们,而程序行为仍然是正确的。

在这里插入图片描述

替换的含义

  • “替换” 并不是说子类的方法和父类的方法完全一样,而是子类的方法应该符合父类的接口约定,且能够在程序的其他部分被透明地替换为子类的实现。
  • 子类替换父类:指的是在程序中,我们可以将父类的对象替换为子类的对象,程序依然能正确执行,而不引发错误或不一致的行为。

关键点:

  • 共性抽取:父类 BankCard 定义了银行卡的共性方法(如存款、查询余额和取款)。无论是储蓄卡还是信用卡,都继承自 BankCard
  • 不影响程序行为:虽然信用卡和储蓄卡的 withdraw 方法逻辑不同,但由于它们都遵循父类 BankCard 的接口规范,在程序中替换时不会引发错误。

代码修改和替换过程

父类 BankCard

BankCard 定义了银行卡的共性操作,比如存款、查询余额和取款。withdraw 作为抽象方法,这样可以明确规定所有的子类都必须提供具体的方法实现,使得设计更加的清晰。

package principles.lsp;

// 抽象类 BankCard
public abstract class BankCard {
    protected String cardNumber;  // 卡号
    protected double balance;     // 余额

    // 构造函数
    public BankCard(String cardNumber, double balance) {
        this.cardNumber = cardNumber;
        this.balance = balance;
    }

    // 存款方法
    public void deposit(double amount) {
        if (amount <= 0) {
            System.out.println("存款金额必须大于零!");
            return;
        }
        balance += amount;
        System.out.println("存款成功!当前余额:" + balance + "元");
    }

    // 查询余额
    public void checkBalance() {
        System.out.println("当前余额:" + balance + "元");
    }

    // 抽象的取款方法,必须由子类实现
    public abstract void withdraw(double amount);
}

子类 SavingsCard(储蓄卡)

SavingsCard 继承 BankCard,重写 withdraw 方法。

package principles.lsp;

// 储蓄卡类,继承自 BankCard
class SavingsCard extends BankCard {

    public SavingsCard(String cardNumber, double balance) {
        super(cardNumber, balance);
    }

    // 实现父类的取款方法
    @Override
    public void withdraw(double amount) {
        if (amount <= 0) {
            System.out.println("取款金额必须大于零!");
            return;
        }
        if (amount > balance) {
            System.out.println("余额不足,取款失败!");
            return;
        }
        balance -= amount;
        System.out.println("取款成功!当前余额:" + balance + "元");
    }
}

子类 CreditCard(信用卡)

CreditCard 继承 SavingsCard,并重写了 withdraw 方法,支持透支。

package principles.lsp;

// 信用卡类,继承自 SavingsCard,并重写取款方法,支持透支
class CreditCard extends SavingsCard {
    private double creditLimit;  // 信用额度

    public CreditCard(String cardNumber, double balance, double creditLimit) {
        super(cardNumber, balance);
        this.creditLimit = creditLimit;
    }

    // 重写取款方法,允许透支
    @Override
    public void withdraw(double amount) {
        if (amount <= 0) {
            System.out.println("取款金额必须大于零!");
            return;
        }
        // 先检查余额,再检查信用额度
        if (amount > balance + creditLimit) {
            System.out.println("余额和信用额度不足,取款失败!");
            return;
        }
        balance -= amount;
        System.out.println("取款成功!当前余额:" + balance + "元(信用额度剩余:" + (creditLimit + balance) + "元)");
    }
}

测试代码

package principles.lsp;

// 测试类
public class BankTest {
    public static void main(String[] args) {
        // 创建储蓄卡和信用卡对象
        BankCard savingsCard = new SavingsCard("123456789", 1500);
        BankCard creditCard = new CreditCard("987654321", 1500, 3000);

        // 储蓄卡操作
        System.out.println("-------- 储蓄卡操作 --------");
        savingsCard.deposit(500);    // 存款
        savingsCard.withdraw(1200);  // 取款
        savingsCard.checkBalance();  // 查询余额

        // 信用卡操作
        System.out.println("\n-------- 信用卡操作 --------");
        creditCard.deposit(500);    // 存款
        creditCard.withdraw(4500);  // 取款(透支)
        creditCard.checkBalance();  // 查询余额

        // 假设在某个地方,我们需要处理储蓄卡和信用卡,但只考虑了父类 BankCard 行为:
        System.out.println("\n-------- 处理银行卡的取款 --------");
        processWithdrawal(savingsCard, 500);  // 传入储蓄卡

        System.out.println("\n-------- 处理银行卡的取款 --------");
        processWithdrawal(creditCard, 500);  // 传入信用卡
    }

    // 处理取款的方法,接受 BankCard 类型参数
    public static void processWithdrawal(BankCard card, double amount) {
        System.out.println("===================");
        System.out.println("处理取款:");
        card.withdraw(amount);  // 调用卡片的 withdraw 方法
    }
}

运行结果及分析:

在这里插入图片描述

  • 通过将信用卡和储蓄卡的共性行为抽取出来统一给它们的抽象父类BankCard,然后通过子类去实现它们各自的方法,使得它们子类之间互不影响并且还可以替换父类的行为。

小结:

  1. 抽象父类 BankCard 的作用

    • 提供了银行卡的统一接口,定义了通用的方法,如 depositwithdrawcheckBalance
    • 所有银行卡(如 SavingsCardCreditCard)都继承自 BankCard,遵循相同的接口。
  2. 子类的行为定制

    • SavingsCardCreditCard 各自根据自己的业务需求重写了 withdraw 方法:
      • SavingsCard 只允许在余额范围内取款;
      • CreditCard 支持在余额加信用额度范围内取款。
  3. 互不影响的原因

    • 接口统一:尽管 SavingsCardCreditCard 的父类,它们都继承自 BankCard,并实现了各自的取款逻辑,互不干扰。
    • 行为替换:可以通过 BankCard 类型的引用分别操作 SavingsCardCreditCard,程序可以根据具体对象执行各自的行为。
    • 里氏替换原则:无论使用 SavingsCard 还是 CreditCard,它们都能正确地执行各自的逻辑,符合预期。
  4. 替换性

    • 可以用 BankCard 类型来替换 SavingsCardCreditCard,它们仍然能按照各自的逻辑工作,保持行为一致性和独立性。

即使 SavingsCardCreditCard 的父类,它们的行为通过共同的父类 BankCard 被规范化,使得不同银行卡可以互换使用,且各自的取款行为独立,不互相干扰

总结

  • 总结来说,LSP原则要求在扩展类时不修改调用方代码,确保子类对象可以替换父类对象。
    • 如果我们不遵循里氏替换原则,通常会导致以下问题:

      • 子类改变父类行为契约,导致使用父类类型的地方不能正确地处理子类对象,或者出现了不符合预期的行为。
      • 程序逻辑依赖于特定的子类行为,而无法正常处理其他类型的子类。

](https://img-home.csdnimg.cn/images/20220524100510.png#pic_center)
](https://img-home.csdnimg.cn/images/20220524100510.png#pic_center)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

I'mAileen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值