lsp
原则 不是你以为的下面这种lsp
, 🐶狗头保命)lsp
原则介绍里氏替换原则(LSP)
,这是SOLID原则之一。该原则由美国女计算机科学家里氏提出,强调在面向对象编程中,子类 可以替换父类的行为,但必须保持一致性。
具体来说,子类的操作结果必须与直接调用父类的操作结果一致。违反这一
原则可能导致不可预期的行为,增加代码复杂度和出错概率。
我们可以通过以下情境来展示如何违反里氏替换原则:
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
,然后通过子类去实现它们各自的方法,使得它们子类之间互不影响并且还可以替换父类的行为。BankCard
的作用:
deposit
、withdraw
和 checkBalance
。SavingsCard
和 CreditCard
)都继承自 BankCard
,遵循相同的接口。SavingsCard
和 CreditCard
各自根据自己的业务需求重写了 withdraw
方法: SavingsCard
只允许在余额范围内取款;CreditCard
支持在余额加信用额度范围内取款。SavingsCard
是 CreditCard
的父类,它们都继承自 BankCard
,并实现了各自的取款逻辑,互不干扰。BankCard
类型的引用分别操作 SavingsCard
和 CreditCard
,程序可以根据具体对象执行各自的行为。SavingsCard
还是 CreditCard
,它们都能正确地执行各自的逻辑,符合预期。BankCard
类型来替换 SavingsCard
或 CreditCard
,它们仍然能按照各自的逻辑工作,保持行为一致性和独立性。即使 SavingsCard
是 CreditCard
的父类,它们的行为通过共同的父类 BankCard
被规范化,使得不同银行卡可以互换使用,且各自的取款行为独立,不互相干扰。