是否有可能在构造函数中违反 Liskov 替换原则?
Is it possible to violate Liskov Substitution Principle in a constructor?
我刚刚安装了 Microsoft Code Contracts。它是 .NET Framework 和 Visual Studio add-on 的一部分。它提供了对已定义合约的运行时检查和静态检查。
该工具有四个警告级别,所以我设置了最高级别。
我已声明 classes 旨在违反 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 状态:
if S is a subtype of T, then objects of type T may be replaced with
objects of type S without altering any of the desirable properties of
that program
在我的示例中,违规行为是此分配:Person person = new Child(23);
。我们应该可以做到这一点,但我们做不到,因为 children 不能比 class 要求的年龄小一些。
然而分析的结果却令人惊讶CodeContracts: Checked 11 assertions: 11 correct
。是我的例子错了还是代码契约没有检测到这样的事情?
虽然 LSP 确实指定子类型不能对方法设置更严格的先决条件,但这不适用于构造函数,因为您不以多态方式使用构造函数。
合同违规将是 new Child(23);
,它发生在分配给 Person
之前。
所以示例违规是错误的,它没有创建子类型 S 的实例,更不用说用 T 替换它了。
我会说 liskov 替换控制 class 的构造实例的行为。因此,一个正确构造的 Child 实例可以毫无问题地替换 Person。
您对如何构建 Child 有限制。我没有看到框架没有将此标记为问题。
有一个著名的鸭子违反 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";
}
}
下面介绍一下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 或 abstract 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 显示警告。
我刚刚安装了 Microsoft Code Contracts。它是 .NET Framework 和 Visual Studio add-on 的一部分。它提供了对已定义合约的运行时检查和静态检查。
该工具有四个警告级别,所以我设置了最高级别。
我已声明 classes 旨在违反 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 状态:
if S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of that program
在我的示例中,违规行为是此分配:Person person = new Child(23);
。我们应该可以做到这一点,但我们做不到,因为 children 不能比 class 要求的年龄小一些。
然而分析的结果却令人惊讶CodeContracts: Checked 11 assertions: 11 correct
。是我的例子错了还是代码契约没有检测到这样的事情?
虽然 LSP 确实指定子类型不能对方法设置更严格的先决条件,但这不适用于构造函数,因为您不以多态方式使用构造函数。
合同违规将是 new Child(23);
,它发生在分配给 Person
之前。
所以示例违规是错误的,它没有创建子类型 S 的实例,更不用说用 T 替换它了。
我会说 liskov 替换控制 class 的构造实例的行为。因此,一个正确构造的 Child 实例可以毫无问题地替换 Person。
您对如何构建 Child 有限制。我没有看到框架没有将此标记为问题。
有一个著名的鸭子违反 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";
}
}
下面介绍一下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 或 abstract 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 显示警告。