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 !
子类型应该像其超类型一样表现(见下文)。
“加强”前置条件的唯一方法是加强不变式。因为不变式应该在每个方法调用之前和之后都为真。通过加强不变式,LSP并不会被违反,因为在方法调用之前,不变式已经免费给出:它在初始化时为真,因此在第一个方法调用之前也为真。因为它是一个不变式,在第一个方法调用之后仍然为真。逐步地,它在下一个方法调用之前始终为真(这是数学归纳法...)。
class CappedAccount extends Account {
[invariant] balance <= cap
}
方法调用前后不变式应该是真实的:
@Override
public void add(double amount) {
assert balance <= cap;
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;
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)
}
实现过程中需要满足不变性条件:
public boolean add(double amount) {
assert balance <= cap;
assert amount >= 0;
double old_balance = balance;
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原始版本中的内容为:“如果对于类型为S的每个对象o_1,都存在类型为T的对象o_2,使得对于所有基于T定义的程序P,在将o_1替换为o_2时,P的行为保持不变,则S是T的子类型”。该规则被违反了。您需要定义一个适用于每个程序的o_2。选择一个上限,比如说1000。我将编写以下程序:
Account a = ...
if (a.add(1001)) {
} else {
}
这不是问题,因为当然,每个人都使用了一个弱化版本的LSP:我们不希望行为保持不变(子类型可能只有有限的兴趣,例如性能,考虑数组列表和链接列表),我们希望保留所有“该程序的可取属性”(请参见此问题)。
add()
不会超过上限,我是否在加强前置条件呢? - Suneet TipirneniAccount
类的合同基本上是说,“如果给定的金额可以添加,那么就会被添加”。它没有指定“可以添加”的含义,因此子类可以自由地添加他们的规则。这应该在合同文档中更清晰地说明;您将在文档中发现类似于“子类可能具有关于有效值的不同条件”的措辞。 - daniu