当前,代码协定不允许在派生类中的成员上有前提条件,前提是该成员已经在基类中设置了前提条件(我目前实际上收到警告,而不是错误)。我不了解其背后的逻辑。我知道这与Liskov的替换规则有关,后者指出派生类应该始终可以在需要父级的地方使用。当然,“已使用”是指按预期工作。对于接口(interface),这对我来说似乎还可以,因为实现接口(interface)的不同类型不会添加状态,因此可以准确地履行契约(Contract)。但是,当您从基类继承时,您这样做是为了添加状态和特殊功能,并且通常情况下,重写方法会有额外的要求。为什么不能像后置条件和对象不变式那样将前置条件与在一起?

请看下面的内容:

class Speaker
{
    public bool IsPlugged { get; set; }
    protected virtual void Beep()
    {
        Contract.Requires(IsPlugged);
        Console.WriteLine("Beep");
    }
}

class WirelessSpeaker : Speaker
{
    public bool TransmitterIsOn { get; set; }
    protected override void Beep()
    {
        Contract.Requires(TransmitterIsOn);
        base.Beep();
    }
}

您可能会争辩说,该类层次结构违反了Liskov的规则,因为无线扬声器在传递给需要Speaker的方法时可能无法发出蜂鸣声。但这不是我们为什么要使用代码契约(Contract)的原因吗?确保满足要求?

最佳答案

代码契约(Contract)不是关于满足要求,而是它们之间的沟通。 Speaker.Beep的调用者受契约(Contract)约束,该契约(Contract)仅在某些情况下有效。
WirelessSpeaker缩小了Speaker的功能空间-这正是Liskov发挥作用的地方。如果我知道它是无线的,那么我只能有效地使用该特定的Speaker。在那种情况下,我应该明确接受WirelessSpeaker,而不是Speaker,并避免替换问题。

编辑以回应评论:
WirelessSpeaker的作者选择如何解释Beep命令。选择一个在此级别可见但在基本级别不可见的新契约(Contract),会施加一些约束,这些约束在使用Speaker时会应用少于100%的时间。

如果只是在发射机未打开时不发出蜂鸣声,我们就不会在谈论代码契约(Contract)。他们的意图不是在运行时进行通信,而是在设计时进行调用的语义(而不仅仅是其语法)进行通信。

异常在运行时发生,最终阻止了“不正确”的调用,这一事实在这里与无关紧要。

10-08 01:58