当前,代码协定不允许在派生类中的成员上有前提条件,前提是该成员已经在基类中设置了前提条件(我目前实际上收到警告,而不是错误)。我不了解其背后的逻辑。我知道这与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)。他们的意图不是在运行时进行通信,而是在设计时进行调用的语义(而不仅仅是其语法)进行通信。
异常在运行时发生,最终阻止了“不正确”的调用,这一事实在这里与无关紧要。