这个前提是否违反里氏替换原则

Is this precondition a violation of the Liskov Substitution Principle

我有 3 个 类、AccountCappedAccountUserAccount

CappedAccountUserAccount 都扩展了 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"(参见 )。