这个前提是否违反里氏替换原则
Is this precondition a violation of the Liskov Substitution Principle
我有 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。
例如,CappedAccount
中的add()
算作"strengthening preconditions"吗?
请务必记住 LSP 涵盖语法和语义。它涵盖了 方法的编码用途, 和 记录了方法的用途。这意味着模糊的文档会使应用 LSP 变得困难。
你怎么理解这个?
Attempts to add money to account.
很明显add()
方法不能保证给账户加钱;所以 CappedAccount.add()
可能实际上没有加钱这一事实似乎是可以接受的。但是没有文件说明当尝试增加资金失败时应该预期的结果。由于该用例未记录,"do nothing" 似乎是一种可接受的行为,因此我们没有违反 LSP。
为了安全起见,我会修改文档以定义失败 add()
的预期行为,即明确定义 post 条件。由于 LSP 涵盖语法和语义,您可以通过修改任何一个来解决违规问题。
TLDR;
if (balance + amount > cap) {
return;
}
不是先决条件而是不变量,因此不违反(他自己)里氏替换原则。
现在,真正的答案。
真正的先决条件是(伪代码):
[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 !
子类型的行为应与其超类型相似(见下文)。
"strengthen"前提条件的唯一方法是加强不变量。因为不变量在每个方法调用之前和之后 应该为真。 LSP 没有被强化的不变量(自己)违反,因为不变量在方法调用之前被 for free 给出:它在初始化时为真,因此在第一个方法之前为真称呼。因为它是不变量,所以在第一次方法调用后为真。并且一步一步,在下一次方法调用之前总是为真(这是一个数学归纳法。。。)。
class CappedAccount extends Account {
[invariant] balance <= cap
}
方法调用前后不变量应为真:
@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"。想知道有没有成功!!让我们在基础 class:
中试试这个
/**
* 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 ("If for each object o_1
of type S
there is an object o_2
of type T
such that for all programs P
defined in terms of T
, the behavior of P
is unchanged when o_1
is substituted for o_2
, then S
is a subtype of T
") 被违反了。您必须定义适用于每个程序的 o_2
。选择一个上限,比方说 1000
。我将编写以下程序:
Account a = ...
if (a.add(1001)) {
// if a = o_2, you're here
} else {
// else you might be here.
}
这不是问题,因为当然每个人都使用 LSP 的弱化版本:我们不希望行为 不变(子类型的兴趣有限,例如性能,想想数组列表与链表)),我们希望保留所有 "the desirable properties of that program"(参见 )。
我有 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。
例如,CappedAccount
中的add()
算作"strengthening preconditions"吗?
请务必记住 LSP 涵盖语法和语义。它涵盖了 方法的编码用途, 和 记录了方法的用途。这意味着模糊的文档会使应用 LSP 变得困难。
你怎么理解这个?
Attempts to add money to account.
很明显add()
方法不能保证给账户加钱;所以 CappedAccount.add()
可能实际上没有加钱这一事实似乎是可以接受的。但是没有文件说明当尝试增加资金失败时应该预期的结果。由于该用例未记录,"do nothing" 似乎是一种可接受的行为,因此我们没有违反 LSP。
为了安全起见,我会修改文档以定义失败 add()
的预期行为,即明确定义 post 条件。由于 LSP 涵盖语法和语义,您可以通过修改任何一个来解决违规问题。
TLDR;
if (balance + amount > cap) {
return;
}
不是先决条件而是不变量,因此不违反(他自己)里氏替换原则。
现在,真正的答案。
真正的先决条件是(伪代码):
[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 !
子类型的行为应与其超类型相似(见下文)。
"strengthen"前提条件的唯一方法是加强不变量。因为不变量在每个方法调用之前和之后 应该为真。 LSP 没有被强化的不变量(自己)违反,因为不变量在方法调用之前被 for free 给出:它在初始化时为真,因此在第一个方法之前为真称呼。因为它是不变量,所以在第一次方法调用后为真。并且一步一步,在下一次方法调用之前总是为真(这是一个数学归纳法。。。)。
class CappedAccount extends Account {
[invariant] balance <= cap
}
方法调用前后不变量应为真:
@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"。想知道有没有成功!!让我们在基础 class:
/**
* 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 ("If for each object o_1
of type S
there is an object o_2
of type T
such that for all programs P
defined in terms of T
, the behavior of P
is unchanged when o_1
is substituted for o_2
, then S
is a subtype of T
") 被违反了。您必须定义适用于每个程序的 o_2
。选择一个上限,比方说 1000
。我将编写以下程序:
Account a = ...
if (a.add(1001)) {
// if a = o_2, you're here
} else {
// else you might be here.
}
这不是问题,因为当然每个人都使用 LSP 的弱化版本:我们不希望行为 不变(子类型的兴趣有限,例如性能,想想数组列表与链表)),我们希望保留所有 "the desirable properties of that program"(参见