我是设计和学习设计原则的新手。
它说从矩形中导出正方形是违反Liskov替代原则的典型例子。
如果是这样,那么正确的设计应该是什么?
答案 0 :(得分:70)
答案取决于可变性。如果你的矩形和方形类是不可变的,那么Square
实际上是Rectangle
的子类型,并且从第二个派生出来是完全可以的。否则,Rectangle
和Square
都可以公开IRectangle
没有变更器,但是从另一个中派生一个是错误的,因为这两种类型都不是另一种类型的正确类型。
答案 1 :(得分:57)
我相信推理是这样的:
假设您有一个接受矩形并调整其宽度的方法:
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);
...因为更改矩形的宽度不会影响其高度。
但是,假设您从Rectangle派生了一个新的Square类。根据定义,正方形的高度和宽度始终相等。让我们再试一次这个测试:
Rectangle rect = new Square(20); // both width and height
SetWidth(rect, 100);
Assert.AreEqual(20, rect.Height);
该测试将失败,因为将正方形的宽度设置为100也会改变其高度。
因此,通过从矩形中导出Square来违反Liskov的替换原则。
“is-a”规则在“真实世界”中是有意义的(正方形绝对是一种矩形),但并不总是在软件设计领域。
修改强>
要回答你的问题,正确的设计应该是Rectangle和Square都来自一个普通的“Polygon”或“Shape”类,它不会强制执行任何有关宽度或高度的规则。
答案 2 :(得分:5)
我不同意从矩形中导出square必然会违反LSP。
在Matt的例子中,如果你的代码依赖于宽度和高度是独立的,那么它确实违反了LSP。
但是,如果您可以在代码中的任何地方用矩形替换正方形而不破坏任何假设,那么您就不会违反LSP。
所以它真正归结为抽象矩形在你的解决方案中的含义。
答案 3 :(得分:3)
我最近一直在努力解决这个问题并且认为我会把我的帽子加入戒指:
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
。通过将“resizableness”移动到子类中,我们可以在实际改进模型的同时重复使用代码。可以这样想:正方形不能在保持正方形的情况下自由调整大小,而非正方形矩形可以。不是所有矩形都可以调整大小,因为 square 是一个矩形(并且在保留其“身份”时不能自由调整大小)。 (o_O)因此,创建一个不可调整大小的基类Rectangle
类是有意义的,因为这是某些矩形的额外属性。
答案 4 :(得分:2)
假设我们有类Rectangle,其中包含两个(为了简单公开)属性width,height。我们可以改变这两个属性:r.width = 1,r.height = 2 现在我们说Square is_a Rectangle。但是虽然声称“方形将像矩形一样”,但我们无法在方形对象上设置.width = 1和.height = 2(如果设置高度,您的类可能会调整宽度,反之亦然)。所以至少有一种情况是Square类型的对象不像Rectangle那样,因此你不能(完全)替换它们。
答案 5 :(得分:1)
我相信存在OOD / OOP技术使软件能够代表现实世界。在现实世界中,正方形是具有相等边的矩形。广场是一个正方形,因为它有相同的边,不是因为它决定是一个正方形。因此,OO计划需要处理它。当然,如果实例化对象的例程希望它是正方形,则可以将length属性和width属性指定为等于相同的量。如果使用该对象的程序稍后需要知道它是否为正方形,则只需要询问它。该对象可以具有名为“Square”的只读布尔属性。当调用例程调用它时,对象可以返回(Length = Width)。即使矩形对象是不可变的,现在也是如此。此外,如果矩形确实是不可变的,则可以在构造函数中设置Square属性的值,并使用它完成。为什么这是一个问题呢? LSP要求子对象是不可变的,而矩形是矩形的子对象通常用作其违反的一个例子。但这似乎不是好设计,因为当使用例程调用对象为“objSquare”时,必须知道它的内部细节。如果它不关心矩形是否是正方形,那不是更好吗?那将是因为矩形的方法无论如何都是正确的。是否存在违反LSP的更好示例?
还有一个问题:对象如何变得不可变?是否存在可以在实例化时设置的“不可变”属性?
我找到了答案,这正是我所期待的。由于我是VB .NET开发人员,这就是我感兴趣的。但是不同语言的概念是相同的。在VB .NET中,您可以通过将属性设置为只读来创建不可变类,并使用New构造函数允许实例化例程在创建对象时指定属性值。您还可以对某些属性使用常量,它们将始终相同。从创建开始,对象是不可变的。
答案 6 :(得分:1)
问题在于所描述的内容实际上不是“类型”,而是累积的新兴属性。
你真正拥有的是四边形,“方形”和“矩形”都只是从角度和边的属性中得出的紧急伪影。
“Square”(甚至是矩形)的整个概念只是对象的属性集合的相对于彼此和所讨论的对象的抽象表示,而不是对象的自身类型。
这是在无类型语言的上下文中思考问题可以帮助的地方,因为它不是确定它是否是“正方形”的类型,而是确定它是否是“正方形”的对象的实际属性。 / p>
我想如果你想进一步抽象你甚至不会说你有一个四边形,但你有一个多边形甚至只是一个形状。
答案 7 :(得分:-3)
非常简单:)这个类(派生链中的第一个)应该是最常用的“基础”。
例如形状 - >矩形 - >方。
这里的正方形是矩形(具有约束尺寸)的特殊情况,而矩形是形状的特殊情况。
说另一种方式 - 使用“是一个”测试。乡绅是一个长方形。但是,矩形并不总是正方形。