前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >【AI时代的设计模式:LSP原则的智能应用】

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

作者头像
ImAileen
发布2024-12-23 08:12:01
发布2024-12-23 08:12:01
7200
代码可运行
举报
运行总次数:0
代码可运行

前言

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

lsp原则介绍

  • 里氏替换原则(LSP),这是SOLID原则之一。该原则由美国女计算机科学家里氏提出,强调在面向对象编程中,子类 可以替换父类的行为,但必须保持一致性。 具体来说,子类的操作结果必须与直接调用父类的操作结果一致。违反这一 原则可能导致不可预期的行为,增加代码复杂度和出错概率。
    • 下面我们将以银行卡中的信用卡和储蓄卡为例,介绍不遵循LSP原则的例子,以及如何通过LSP进行优化。

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

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

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

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

储蓄卡
代码语言:javascript
代码运行次数:0
运行
复制
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 + "元");
    }
}
信用卡
代码语言:javascript
代码运行次数:0
运行
复制
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) + "元)");
    }
}
测试类
代码语言:javascript
代码运行次数:0
运行
复制
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 作为抽象方法,这样可以明确规定所有的子类都必须提供具体的方法实现,使得设计更加的清晰。

代码语言:javascript
代码运行次数:0
运行
复制
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 方法。

代码语言:javascript
代码运行次数:0
运行
复制
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 方法,支持透支。

代码语言:javascript
代码运行次数:0
运行
复制
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) + "元)");
    }
}
测试代码
代码语言:javascript
代码运行次数:0
运行
复制
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原则要求在扩展类时不修改调用方代码,确保子类对象可以替换父类对象。
    • 如果我们不遵循里氏替换原则,通常会导致以下问题:
      • 子类改变父类行为契约,导致使用父类类型的地方不能正确地处理子类对象,或者出现了不符合预期的行为。
      • 程序逻辑依赖于特定的子类行为,而无法正常处理其他类型的子类。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • lsp原则介绍
  • 不遵循里氏替换原则(LSP)的例子
  • 代码实现:不遵循里氏替换原则
    • 储蓄卡
    • 信用卡
    • 测试类
  • 运行结果及分析
  • 遵循lsp的新设计
    • 替换:
    • 替换的含义
    • 关键点:
  • 代码修改和替换过程
    • 父类 BankCard
    • 子类 SavingsCard(储蓄卡)
    • 子类 CreditCard(信用卡)
    • 测试代码
    • 运行结果及分析:
    • 小结:
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档