我有3个类,Account
,CappedAccount
,UserAccount
,CappedAccount
和UserAccount
都扩展了Account
。Account
包含以下内容:
abstract class Account {
...
/**
* Attempts to add money to account.
*/
public void add(double amount) {
balance += amount;
}
}
CappedAccount
覆盖此行为:public class CappedAccount extends Account {
...
@Override
public void add(double amount) {
if (balance + amount > cap) { // New Precondition
return;
}
balance += amount;
}
}
UserAccount
不会覆盖Account
的任何方法,因此无需声明。我的问题是,
CappedAccount#add
是否违反LSP,如果确实违反了LSP,我如何设计它以符合LSP。例如,
add()
中的CappedAccount
是否算作“加强前提”? 最佳答案
TLDR;
if (balance + amount > cap) {
return;
}
不是先决条件,而是是不变的,因此(本身)不违反Liskov替代原理。
现在,实际答案。
一个真正的前提是(伪代码):
[requires] balance + amount <= cap
您应该能够强制执行此先决条件,即检查条件并在不满足条件的情况下引发错误。如果您确实执行了前提条件,则会看到违反了LSP:
Account a = new Account(); // suppose it is not abstract
a.add(1000); // ok
Account a = new CappedAccount(100); // balance = 0, cap = 100
a.add(1000); // raise an error !
子类型的行为应类似于其父类型(请参见下文)。
“加强”前提的唯一方法是加强不变式。因为在每个方法调用之前和之后,不变性均应为true。增强的不变式不会违反LSP(自己),因为在方法调用之前就免费提供了不变式:初始化时为真,因此在第一个方法调用之前为真。因为它是不变的,所以在第一个方法调用之后它是正确的。在下一个方法调用之前,逐步操作始终是正确的(这是一个数学归纳法...)。
class CappedAccount extends Account {
[invariant] balance <= cap
}
在方法调用之前和之后,不变量应为true:
@Override
public void add(double amount) {
assert balance <= cap;
// code
assert balance <= cap;
}
您将如何在
add
方法中实现呢?您有一些选择。可以的:@Override
public void add(double amount) {
assert balance <= cap;
if (balance + amount <= cap) {
balance += cap;
}
assert balance <= cap;
}
嘿,但这就是你所做的! (有一点区别:此导出有一个导出可检查不变量。)
这也是,但语义不同:
@Override
public void add(double amount) {
assert balance <= cap;
if (balance + amount > cap) {
balance = cap;
} else {
balance += cap;
}
assert balance <= cap;
}
这也是,但语义是荒谬的(或关闭帐户?):
@Override
public void add(double amount) {
assert balance <= cap;
// do nothing
assert balance <= cap;
}
好的,您添加了一个不变式,而不是前提条件,这就是为什么不违反LSP的原因。答案结束。
但是...这并不令人满意:
add
“试图向帐户添加资金”。我想知道这是否成功!让我们在基类中尝试一下:/**
* Attempts to add money to account.
* @param amount the amount of money
* @return True if the money was added.
*/
public boolean add(double amount) {
[requires] amount >= 0
[ensures] balance = (result && balance == old balance + amount) || (!result && balance == old balance)
}
以及实现,带有不变式:
/**
* Attempts to add money to account.
* @param amount the amount of money
* @return True is the money was added.
*/
public boolean add(double amount) {
assert balance <= cap;
assert amount >= 0;
double old_balance = balance; // snapshot of the initial state
bool result;
if (balance + amount <= cap) {
balance += cap;
result = true;
} else {
result = false;
}
assert (result && balance == old balance + amount) || (!result && balance == old balance)
assert balance <= cap;
return result;
}
当然,除非您使用Eiffel(这可能是个好主意),否则没有人会像那样编写代码,但是您会看到这个主意。这是一个没有所有条件的版本:
public boolean add(double amount) {
if (balance + amount <= cap) {
balance += cap;
return true;
} else {
return false;
}
请注意LSP的原始版本(“对于每个类型为
o_1
的对象S
,都有一个对象类型为o_2
的T
,这样,对于用P
定义的所有程序T
,当用P
代替o_1
时,o_2
的行为不会改变。 S
,则T
是o_2
的子类型“)被违反。您必须定义适用于每个程序的1000
。选择一个上限,比如ojit_code。我将编写以下程序:Account a = ...
if (a.add(1001)) {
// if a = o_2, you're here
} else {
// else you might be here.
}
这不是问题,因为,当然,每个人都使用LSP的弱化版本:我们不希望行为者成为不变(子类型的兴趣有限,例如性能,考虑数组列表与链接列表) ),我们希望保留所有“该程序的理想属性” (请参见this question)。