代码约定和继承(重写方法的前提条件)

Far*_*ori 5 .net c# liskov-substitution-principle code-contracts solid-principles

目前代码契约不允许派生类中成员的前提条件,其中成员已经在基类中设置了前提条件(我实际上当前得到警告而不是错误).我不明白这背后的逻辑.我理解它与Liskov的替换规则有关,声明派生类应始终能够在父预期的地方使用.当然"使用"意味着按预期工作.对于接口而言,这对我来说似乎没问题,因为实现接口的不同类型不会添加状态,因此可以完全强制合同.但是,当您从基类继承时,您正在这样做以添加状态和特殊功能,并且通常情况下,覆盖方法会有额外的要求.为什么不能像前置条件和对象不变量一样将前置条件与AND组合在一起?

看看下面:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

你可能会争辩说这个类层次结构打破了Liskov的规则,因为当传递给期望a的方法时,无线扬声器可能无法发出蜂鸣声Speaker.但这不是我们使用代码合同的原因吗?确保满足要求?

Bry*_*tts 9

代码合同不是关于要求的满足,而是关于它们的沟通.呼叫者Speaker.Beep受合同约束,该合同仅在某些情况下生效.

WirelessSpeaker 缩小SpeakerLiskov发挥作用的功能空间.Speaker如果我知道它是无线的,我只能有效地使用它.在这种情况下,我应该明确接受WirelessSpeaker,而不是Speaker,并避免替代问题.

编辑以回应评论:

作者WirelessSpeaker选择如何解释Beep命令.选择一个新的合同,在此级别可见但不在基础级别,强加约束,在使用Speakers 时<100%应用.

如果在发射器未开启时根本不发出蜂鸣声,我们就不会谈论代码合同.他们的目的不是在运行时进行通信,而是在设计时,调用语义(而不仅仅是语法).

在运行时发生异常,最终阻止"不正确"调用这一事实在很大程度上与此无关.