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

时间:2016-03-05 15:37:12

标签: c# code-contracts solid-principles liskov-substitution-principle

我刚刚安装了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;
}

LSP声明:

  

如果S是T的子类型,则T类型的对象可以替换为   S类型的对象没有改变任何理想的属性   该计划

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

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

3 个答案:

答案 0 :(得分:15)

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

合同违规将new Child(23);在分配到Person之前发生。

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

答案 1 :(得分:6)

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

enter image description here

然而,我们不能在构造函数中违反它。假设我们有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";
    }
}

现在让我们介绍一下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; }
}

首先看起来它似乎违反了LSP,因为ElectricDuck需要的不仅仅是WildDuck或抽象Duck。但只要ElectricDuck提供Quack方法而没有其他要求,它就不是真的。

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

public void Glow()
{
    Contract.Requires(!this.Battery.IsEmpty);
}

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

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

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

答案 2 :(得分:2)

我想说liskov替换会控制构造的类实例的行为。因此,正确构造的Child实例可以替代没有问题的Person。

您对如何构建Child有约束。我没有看到框架没有将此标记为问题。