在 Java 中绕过子类中的最终方法?
Go around a final method in subclass in Java?
我目前正在学习继承,我对在超类中声明方法 final 的限制适用于子类感到有点困惑。
假设我有一个带有提款方法的超类 BankAccount,它需要用户密码和提款金额,并将帐户余额设置为(余额 - 金额)。我想将该方法声明为最终方法,以便其他子类不会潜在地覆盖它并允许客户在不更改帐户余额的情况下取款。
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.");
}
}
其他人建议更改您的设计,但了解您的原始设计为何不起作用很重要。
当您尝试透支时,您的 BankAccount
class 会以某种方式表现。该行为是其隐式 API 的一部分。允许透支的 class 通过 weakening the postcondition 打破 API 余额不能为负。从某种意义上说,它不是一个BankAccount
。因此,它不应该是 BankAccount
的子 class。 class 或方法 final
是 Java 执行此操作的方式。
正如 biziclop 所指出的,可以使用继承来表示可以透支的帐户与不能透支的帐户之间的关系。但是,让其中一个成为另一个的父级违反了 Liskov 替换原则。相反,让两个 classes 实现或扩展一个公共接口或 superclass 不指定透支行为。也许 superclass 根本不应该包含 withdraw
方法。避免写一个可以双向工作的 class。如果您 API 的客户必须测试对象的行为和功能,那么您就绕过了 Java 最大优势之一的强类型。
有一本名为 Effective Java 的好书,每个 Java 程序员都应该阅读。它谈到如何 inheritance breaks encapsulation 以及每个允许继承的 class 必须非常小心地设计。出于这些原因,我认为继承是介绍性编程中首先教授的内容之一,这对学生来说是一种可怕的伤害 class。它应该是最后一个。
您要问的问题:透支账户的取款流程是否不同?不,不是真的,过程是一样的:你检查密码,你检查有足够的资金,你借记账户。
不同的是 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;
}
这样你的 superclass 就不必知道透支限额或任何事情。您可以将锁定帐户实现为它的子 class,它拒绝所有提款请求,或者您可以将任何其他逻辑放入 checkFundsAvailable()
.
P.s.: 尽管有以上所有内容,但有一个很好的理由不通过继承来解决这个问题(如果这是一个真正的问题,而不仅仅是一个练习),但它更微妙。通过拥有 BankAccount
class 和 BankAccountOverdraft
subclass,您还声称 不允许透支的帐户永远不会变成可透支的帐户,反之亦然。但真实的银行账户不会这样,您可以从不允许透支开始,然后再商定透支限额。继承无法表达这一点,您需要使用某种形式的组合。
我假设你的例子只是为了说明设计问题。通常,您不会使用像字符串比较和 API 限制这样简单的东西来保护像银行帐户这样的资源。还有许多其他问题,例如竞争条件。
您可以将 withdraw
分成两个单独的方法。 super
方法可以标记为 final
,您可以使用第二个未标记为 final
的 protected
方法来执行实际撤回。这会强制进行检查,但允许实际的取款行为是动态的。
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.");
}
}
在推导中 class...
@Override
protected void withdraw(double amount) {
if (getOverDraftLimit() + getBalance() >= amount) {
setBalance(getBalance() - amount);
} else {
System.out.println("Rejected.");
}
}
我应该补充一点,这个问题有很多不同的方法,解决方案取决于总体目标。继承并不总是添加动态行为的正确方法。上面的问题之一是它需要添加一个新的派生 class 来更改提取行为,这可能导致过于复杂的 class 层次结构。向 class 添加透支金额将允许相同的金额,并避免许多额外的继承问题。
我目前正在学习继承,我对在超类中声明方法 final 的限制适用于子类感到有点困惑。 假设我有一个带有提款方法的超类 BankAccount,它需要用户密码和提款金额,并将帐户余额设置为(余额 - 金额)。我想将该方法声明为最终方法,以便其他子类不会潜在地覆盖它并允许客户在不更改帐户余额的情况下取款。
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.");
}
}
其他人建议更改您的设计,但了解您的原始设计为何不起作用很重要。
当您尝试透支时,您的 BankAccount
class 会以某种方式表现。该行为是其隐式 API 的一部分。允许透支的 class 通过 weakening the postcondition 打破 API 余额不能为负。从某种意义上说,它不是一个BankAccount
。因此,它不应该是 BankAccount
的子 class。 class 或方法 final
是 Java 执行此操作的方式。
正如 biziclop 所指出的,可以使用继承来表示可以透支的帐户与不能透支的帐户之间的关系。但是,让其中一个成为另一个的父级违反了 Liskov 替换原则。相反,让两个 classes 实现或扩展一个公共接口或 superclass 不指定透支行为。也许 superclass 根本不应该包含 withdraw
方法。避免写一个可以双向工作的 class。如果您 API 的客户必须测试对象的行为和功能,那么您就绕过了 Java 最大优势之一的强类型。
有一本名为 Effective Java 的好书,每个 Java 程序员都应该阅读。它谈到如何 inheritance breaks encapsulation 以及每个允许继承的 class 必须非常小心地设计。出于这些原因,我认为继承是介绍性编程中首先教授的内容之一,这对学生来说是一种可怕的伤害 class。它应该是最后一个。
您要问的问题:透支账户的取款流程是否不同?不,不是真的,过程是一样的:你检查密码,你检查有足够的资金,你借记账户。
不同的是 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;
}
这样你的 superclass 就不必知道透支限额或任何事情。您可以将锁定帐户实现为它的子 class,它拒绝所有提款请求,或者您可以将任何其他逻辑放入 checkFundsAvailable()
.
P.s.: 尽管有以上所有内容,但有一个很好的理由不通过继承来解决这个问题(如果这是一个真正的问题,而不仅仅是一个练习),但它更微妙。通过拥有 BankAccount
class 和 BankAccountOverdraft
subclass,您还声称 不允许透支的帐户永远不会变成可透支的帐户,反之亦然。但真实的银行账户不会这样,您可以从不允许透支开始,然后再商定透支限额。继承无法表达这一点,您需要使用某种形式的组合。
我假设你的例子只是为了说明设计问题。通常,您不会使用像字符串比较和 API 限制这样简单的东西来保护像银行帐户这样的资源。还有许多其他问题,例如竞争条件。
您可以将 withdraw
分成两个单独的方法。 super
方法可以标记为 final
,您可以使用第二个未标记为 final
的 protected
方法来执行实际撤回。这会强制进行检查,但允许实际的取款行为是动态的。
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.");
}
}
在推导中 class...
@Override
protected void withdraw(double amount) {
if (getOverDraftLimit() + getBalance() >= amount) {
setBalance(getBalance() - amount);
} else {
System.out.println("Rejected.");
}
}
我应该补充一点,这个问题有很多不同的方法,解决方案取决于总体目标。继承并不总是添加动态行为的正确方法。上面的问题之一是它需要添加一个新的派生 class 来更改提取行为,这可能导致过于复杂的 class 层次结构。向 class 添加透支金额将允许相同的金额,并避免许多额外的继承问题。