是否有可能在构造函数中违反Liskov替换原则?

Lan*_*eyo 4 c# liskov-substitution-principle code-contracts solid-principles

我刚刚安装了Microsoft Code Contracts.它是.NET Framework和Visual Studio插件的一部分.它提供运行时检查和定义合同的静态检查.

该工具有四个警告级别,所以我设置最高.

我已经宣布了违反Liskov替代原则的课程.

public class Person
{
    protected int Age { get; set; }

    public Person(int age)
    {
        Contract.Requires(age > 0);
        Contract.Requires(age < 130);
        this.Age = age;
    }
}

public class Child : Person
{
    public Child(int age) : base(age)
    {
        Contract.Requires(age > 0); 
        Contract.Requires(age < Consts.AgeOfMajority);
        Contract.Requires(age < 130);
        this.Age = age;
    }
}

public static class Consts
{
    public readonly static int AgeOfMajority = 18;
}
Run Code Online (Sandbox Code Playgroud)

LSP声明:

如果S是T的子类型,那么类型T的对象可以用类型S的对象替换而不改变该程序的任何所需属性

在我的例子中,违规将是这个对齐:Person person = new Child(23);.我们应该能够做到这一点,但我们不能因为孩子不能比某个年龄小于人类所要​​求的年龄.

然而,分析结果令人惊讶CodeContracts: Checked 11 assertions: 11 correct.我的示例是错误的还是Code Contracts没有检测到这样的事情?

wes*_*ton 15

虽然LSP确定子类型不能在方法上放置更多限制性前置条件,但这不适用于构造函数,因为您不以多态方式使用构造函数.

合同违规将new Child(23);在分配给a之前发生Person.

因此,示例违规是错误的,它不会创建子类型S的实例,更不用说为T替换它.

  • 这只是合乎逻辑的。LSP 谈论使用子类型的实例。如果您无法创建子类型的实例,您甚至无法进入 LSP 域。 (2认同)

Lan*_*eyo 6

有一个着名的LSP违规示例:

在此输入图像描述

但是,我们不能在构造函数中违反它.假设我们有Duck和WildDuck类:

public abstract class Duck
{
    public abstract string Quack();
    public double Weight { get; set; }

    public Duck(double weight)
    {
        Contract.Requires(weight > 0);
        this.Weight = weight;
    }
}

public class WildDuck : Duck
{
    public WildDuck(double weight)
        : base(weight)
    {
        Contract.Requires(weight > 0);
        this.Weight = weight;
    }

    public override string Quack()
    {
        return "wild quack";
    }
}
Run Code Online (Sandbox Code Playgroud)

现在让我们介绍一下ElectricDuck:

public class ElectricDuck : Duck
{
    public Battery Battery { get; set; }

    public override string Quack()
    {
        return "electric quack";
    }

    public ElectricDuck(double weight, Battery battery)
        : base(weight)
    {
        Contract.Requires(weight > 0);
        Contract.Requires(battery != null);
        this.Weight = weight;
        this.Battery = battery;
    }
}

public class Battery
{
    public bool IsEmpty { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

在第一眼看来它似乎违反了LSP,因为ElectricDuck需要的不仅仅是WildDuck或抽象Duck.但只要ElectricDuck提供没有额外要求的Quack方法,它就不是真的.

如果ElectricDuck要求电池发光 - 从LSP的角度看它是完全正确的:

public void Glow()
{
    Contract.Requires(!this.Battery.IsEmpty);
}
Run Code Online (Sandbox Code Playgroud)

当我们将要求添加到继承方法时违反了LSP:

public override string Quack()
{
    Contract.Requires(!this.Battery.IsEmpty);
    return "electric quack";
}
Run Code Online (Sandbox Code Playgroud)

此修改将导致CodeContracts显示警告.