我现在正在学习继承,我对在超类中声明一个方法final的限制适用于子类有点困惑。 假设我有一个带退出方法的超类BankAccount,需要提取用户密码和金额,并将账户余额设置为(余额 - 金额)。我想声明该方法是final,以便其他子类不会覆盖它并允许客户端在不改变帐户余额的情况下提取资金。
public final void withdraw(double amount, String pass) {
if (checkPassword(pass) && getBalance() >= amount;) {
setBalance(getBalance() - amount);
} else {
System.out.println("Rejected.");
}
}
我想避免这样的事情被允许:
public void withdraw(double amount, String pass) {
}
然而,一些银行账户允许透支,在提款时也必须考虑透支。现在,如果我有一个子类BankAccountOverdraft,继承的withdraw方法是最终的,所以我将无法更改它的任何部分。但是,我必须考虑子类中的透支限制吗?我怎么能这样做呢?
public void withdraw(double amount, String pass) {
if (checkPassword(pass) && getOverDraftLimit() + getBalance() >= amount) {
setBalance(getBalance() - amount);
} else {
System.out.println("Rejected.");
}
}
答案 0 :(得分:3)
其他人建议改变你的设计,但重要的是要理解为什么你的原始设计不起作用。
当您尝试透支时,BankAccount
课程会以某种方式行事。该行为是其隐式API的一部分。允许透支的类通过weakening the postcondition将API分解为余额不能为负数。从某种意义上说,不是 a BankAccount
。因此,它不应该是BankAccount
的子类。使类或方法final
成为Java的强制执行方式。
正如biziclop指出的那样,可以使用继承来表达可以透支的账户与不能透支的账户之间的关系。但是让任何一方成为另一方的父母,就会打破Liskov替代原则。相反,使两个类实现或扩展不指定透支行为的公共接口或超类。也许超类不应该包含withdraw
方法。避免编写一个双向工作的类。如果您的API的客户端必须测试对象的行为和功能,那么您已经规避了Java最大优势之一的强类型。
每个Java程序员都应该阅读一本名为 Effective Java 的好书。它讨论了inheritance breaks encapsulation如何以及如何非常谨慎地设计每个允许继承的类。由于这些原因,我认为继承是入门编程课程中教授的第一件事,这对学生来说是一种可怕的伤害。它应该是最后一个。
答案 1 :(得分:3)
您需要问的问题是:透支帐户的提款程序是否不同?不,它不是真的,过程是一样的:你检查密码,你检查有足够的资金,你借记帐户。
有什么不同是check there are enough funds
步骤。因此,您的抽象应该反映这一点,就像checkPassword()
一样。
public final void withdraw(double amount, String pass) {
if (checkPassword(pass) && checkFundsAvailable(amount)) {
setBalance(getBalance() - amount);
} else {
System.out.println("Rejected.");
}
}
protected boolean checkFundsAvailable(double amount) {
return amount <= getBalance();
}
当你可以透支时,你可以用以下方式覆盖它:
protected boolean checkFundsAvailable(double amount) {
return amount <= getBalance() + overdraftLimit;
}
这样你的超类就不必了解透支限制或任何真正的限制。您可以将锁定的帐户实现为它的子类,拒绝所有撤销请求,或者您可以将任何其他逻辑放入checkFundsAvailable()
。
P.s。:尽管如此,有一个很好的理由不通过继承来解决这个问题(如果这是一个真正的问题而不仅仅是一个练习),但它更加微妙。通过拥有BankAccount
类和BankAccountOverdraft
子类,您还声称不允许透支的帐户永远不会变成可过度抽取的,反之亦然。但真正的银行账户不是那样的行为,你可以从没有允许透支开始,并在以后同意透支限额。继承没有办法表达这一点,你需要使用某种形式的组合。
答案 2 :(得分:0)
我将假设你的例子只是为了说明设计问题。通常情况下,您不会像字符串比较和API限制那样简单地保护像银行帐户这样的资源。还有很多其他问题,如竞争条件。
您可以做的是将withdraw
分成两个单独的方法。 super
方法可以标记为final
,您可以使用第二个protected
方法,该方法不会标记final
进行实际提款。这会强制检查发生,但允许实际的撤销行为是动态的。
public final void withdraw(double amount, String pass) {
if (checkPassword(pass)) {
withdraw(amount);
} else {
System.out.println("Rejected.");
}
}
protected void withdraw(double amount) {
if (getBalance() >= amount) {
setBalance(getBalance() - amount);
} else {
System.out.println("Rejected.");
}
}
在派生班......
@Override
protected void withdraw(double amount) {
if (getOverDraftLimit() + getBalance() >= amount) {
setBalance(getBalance() - amount);
} else {
System.out.println("Rejected.");
}
}
我应该补充说,这个问题有很多不同的方法,解决方案取决于整体目标。继承并不总是添加动态行为的正确方法。上面的一个问题是它需要添加一个新的派生类来改变撤销行为,这可能导致过于复杂的类层次结构。向该类添加透支金额将允许相同,并避免许多增加的继承问题。