从矩形导出平方是否违反了李斯科夫的替代原则?

我是新来的设计和学习的设计原则。

它说从矩形导出平方是违反 Liskov 替代原则的一个经典例子。

如果是这样的话,那么正确的设计应该是什么呢?

19035 次浏览

它非常简单:)类的‘基’越多(派生链中的第一个)应该是最通用的。

例如,形状-> 矩形-> 正方形。

在这里,正方形是矩形的特殊情况(具有约束的尺寸) ,矩形是形状的特殊情况。

换个说法,用“是”这个测试。乡绅是一个长方形。但矩形并不总是正方形。

让我们假设我们有一个矩形类,它有两个属性(为了简单起见,是公共的) width,height。我们可以改变这两个属性: r.width = 1,r.height = 2。
现在我们说一个正方形是一个矩形。但是,尽管声明是“一个正方形的行为将像一个矩形”,我们不能设置。宽度 = 1及。在一个正方形对象上高度 = 2(如果您设置高度,那么您的类可能会调整宽度,反之亦然)。因此,至少有一种情况下,Square 类型的对象的行为不像矩形,因此您不能(完全)替换它们。

我相信原因是这样的:

假设您有一个接受矩形并调整其宽度的方法:

public void SetWidth(Rectangle rect, int width)
{
rect.Width = width;
}

考虑到矩形是什么,假设这个测试会通过应该是完全合理的:

Rectangle rect = new Rectangle(50, 20); // width, height


SetWidth(rect, 100);


Assert.AreEqual(20, rect.Height);

因为改变矩形的宽度不会影响它的高度。

但是,让我们假设您已经从 Recangle 派生了一个新的 Square 类。根据定义,正方形的高度和宽度总是相等的。让我们再试一次:

Rectangle rect = new Square(20); // both width and height


SetWidth(rect, 100);


Assert.AreEqual(20, rect.Height);

这个测试会失败,因为将一个正方形的宽度设置为100也会改变它的高度。

因此,利斯科夫的替代原则违反了从矩形导出平方。

“ is-a”规则在“现实世界”中是有意义的(正方形肯定是一种矩形) ,但在软件设计世界中并不总是如此。

剪辑

为了回答你的问题,正确的设计可能应该是矩形和正方形都派生自一个公共的“多边形”或“形状”类,它不强制任何关于宽度或高度的规则。

答案取决于可变性。如果您的矩形和方形类是不可变的,那么 Square实际上是 Rectangle的一个子类型,从第二个类派生第一个类是完全可以的。否则,RectangleSquare都可能暴露一个没有突变体的 IRectangle,但是从另一个中派生一个是错误的,因为这两个类型都不是另一个的子类型。

我不同意从矩形导出正方形必然违反 LSP。

在 Matt 的例子中,如果您的代码依赖于宽度和高度是独立的,那么它实际上违反了 LSP。

但是,如果您可以在代码中的任何地方用矩形替换正方形,而不打破任何假设,那么您就不违反 LSP。

所以它实际上归结为抽象矩形在 你的解决方案中的意义。

我相信 OOD/OOP 技术的存在是为了使软件能够表现真实的世界。在现实世界中,正方形是一个边长相等的矩形。正方形之所以是正方形,仅仅是因为它有相等的边,而不是因为它决定成为正方形。因此,OO 程序需要处理它。当然,如果实例化对象的例程希望它是平方的,它可以指定长度属性和宽度属性等于相同的数量。如果使用该对象的程序稍后需要知道它是否为方形,它只需要询问它。该对象可以具有一个名为“ Square”的只读布尔属性。当调用例程调用它时,对象可以返回(Llength = Width)。现在,即使矩形对象是不可变的,情况也是如此。此外,如果矩形确实是不可变的,则可以在构造函数中设置 Square 属性的值并使用它。那么,为什么这是一个问题呢?LSP 要求子对象是不可变的,而作为矩形的子对象的 square 经常被用来作为违背它的例子。但这似乎不是一个好的设计,因为当 using 例程调用对象为“ objecSquare”时,必须知道它的内部细节。如果它不在乎矩形是否是正方形,不是更好吗?这是因为无论如何矩形的方法都是正确的。有没有更好的例子说明何时违反了 LSP?

还有一个问题: 对象是如何变成不可变的?是否有可以在实例化时设置的“ Immutable”属性?

我找到了答案,这正是我所期待的。因为我是 VB。NET 开发人员,这就是我感兴趣的。但是不同语言的概念是相同的。在 VB 中。NET 创建不可变类,方法是将属性设置为只读,并使用 New 构造函数允许实例化例程在创建对象时指定属性值。您还可以对某些属性使用常量,而且它们总是相同的。从创建开始,对象就是不可变的。

问题是,所描述的实际上不是一个“类型”,而是一个累积的紧急属性。

你真正拥有的只是一个四边形,而“正方形”和“矩形”都只是从角度和边的属性衍生出来的自然产物。

“正方形”(甚至是矩形)的整个概念只是一个抽象的对象属性集合的相互关系和问题对象的表示,而不是对象的类型和它的自身。

这就是在无类型语言的上下文中思考问题的帮助所在,因为决定它是否是“一个正方形”的不是类型,而是对象的实际属性决定它是否是“一个正方形”。

我想如果你想进一步抽象,你甚至不会说你有一个四边形,但你有一个多边形,甚至只是一个形状。

我最近一直在为这个问题苦苦挣扎,我想我应该加入这个行列:

public class Rectangle {


protected int height;
protected int width;


public Rectangle (int height, int width) {
this.height = height;
this.width = width;
}


public int computeArea () { return this.height * this.width; }
public int getHeight () { return this.height; }
public int getWidth () { return this.width; }


}


public class Square extends Rectangle {


public Square (int sideLength) {
super(sideLength, sideLength);
}


}


public class ResizableRectangle extends Rectangle {


public ResizableRectangle (int height, int width) {
super(height, width);
}


public void setHeight (int height) { this.height = height; }
public void setWidth (int width) { this.width = width; }


}

注意最后一个类 ResizableRectangle。通过将“可调整大小”移动到子类中,我们可以在实际改进模型的同时获得代码重用。可以这样想: 一个正方形不能自由调整大小,而保持一个正方形,而非正方形可以。但是 所有矩形不能调整大小,因为 正方形是一个矩形(在保留其“身份”的同时,它不能自由调整大小)。(o _ O)因此有必要创建一个不可调整大小的基 Rectangle类,因为这是 一些矩形的一个额外属性。