对不起,如果这个问题似乎太长了。在我问它之前,我需要说明它来自哪里。
设置:
给定以下不可变类型Rectangle
:
class Rectangle
{
public Rectangle(double width, double height) { … }
public double Width { get { … } }
public double Height { get { … } }
}
…从中派生一个类型似乎是完全合法的Square
:
using System.Diagnostics.Contracts;
class Square : Rectangle
{
public Square(double sideLength) : base(sideLength, sideLength) { }
[ContractInvariantMethod]
void WidthAndHeightAreAlwaysEqual()
{
Contract.Invariant(Width == Height);
}
}
…因为派生类可以确保它自己的不变量永远不会被违反。
但是一旦我变得Rectangle
可变:
class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
…
}
......我不应该再从中派生Square
,因为Square
不应该有独立的设置器Width
and Height
。
问题:
我可以用代码合同做什么,以便一旦我Square
从可变Rectangle
类派生它就会警告我违反合同?最好,Code Contracts 的静态分析已经在编译时给了我一个警告。
换句话说,我的目标是使用代码合同对以下规则进行编码:
Width
和Height
aRectangle
可以相互独立地改变。Width
和Height
aSquare
不能相互独立地改变,这首先是没有意义的。
......并以这样一种方式进行,即每当这些规则“冲突”时,代码合同都会注意到。
到目前为止我所考虑的:
1. 添加一个不变量Rectangle
:
class Rectangle
{
…
[ContractInvariantMethod]
void WidthAndHeightAreIndependentFromOneAnother()
{
Contract.Invariant(Width != Height || Width == Height);
}
}
这种方法的问题在于,虽然不变量正确地说明了“宽度和高度不必相等,但它们可以相等”,但它是无效的,(1)因为它是重言式,(2)因为它比Width == Height
派生类中的不变量限制更少Square
。也许它甚至在代码契约看到它之前就被编译器优化掉了。
2. 给Rectangle
setter 添加后置条件:
public double Width
{
get { … }
set { Contract.Ensures(Height == Contract.OldValue(Height)); … }
}
public double Height
{
get { … }
set { Contract.Ensures(Width == Contract.OldValue(Width)); … }
}
这将禁止派生类简单Square
地更新Height
为Width
无论何时Width
更改,反之亦然,它本身Square
不会阻止我从Rectangle
. 但这就是我的目标:让代码合同警告我Square
不能从可变的Rectangle
.