The answer depends on mutability. If your rectangle and square classes are immutable, then Square is really a subtype of Rectangle and it's perfectly OK to derive first from second. Otherwise, Rectangle and Square could both expose an IRectangle with no mutators, but deriving one from the other is wrong since neither type is properly a subtype of the other.
Let's say you have a method that accepts a rectangle and adjusts its width:
public void SetWidth(Rectangle rect, int width)
{
rect.Width = width;
}
It should be perfectly reasonable, given what a rectangle is, to assume that this test would pass:
Rectangle rect = new Rectangle(50, 20); // width, height
SetWidth(rect, 100);
Assert.AreEqual(20, rect.Height);
... because changing a rectangle's width does not affect its height.
However, let's say you've derived a new Square class from Rectangle. By definition, a square has height and width always equal. Let's try that test again:
Rectangle rect = new Square(20); // both width and height
SetWidth(rect, 100);
Assert.AreEqual(20, rect.Height);
That test will fail, because setting a square's width to 100 will also change its height.
Thus, Liskov's substitution principle is violated by deriving Square from Rectangle.
The "is-a" rule makes sense in the "real world" (a square is definitely a kind of rectangle), but not always in the world of software design.
Edit
To answer your question, the correct design should probably be that both Rectangle and Square derive from a common "Polygon" or "Shape" class, which does not enforce any rules regarding width or height.
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; }
}
I've been struggling with this problem a lot lately and thought I'd add my hat into the ring:
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; }
}
Notice the last class, ResizableRectangle. By moving the "resizableness" into a subclass, we get code re-use while actually improving our model. Think of it like this: a square cannot be freely resized while remaining a square, whereas non-square rectangles can. Not all rectangles can be resized though, since a square is a rectangle (and it cannot be freely resized while retaining its "identity"). (o_O) So it makes sense to make a base Rectangle class which is not resizable, since this is an extra property of some rectangles.
The problem is that what is being described is really not a "type" but an cumulative emergent property.
All you really have is a quadrilateral and that both "squareness" and "rectangleness" are just emergent artifacts derived from properties of the angles and sides.
The entire concept of "Square" (or even rectangle) is just an abstract representation of a collection of properties of the object in relation to each other and the object in question, not the type of object in and of it's self.
This is where thinking of the problem in the context of a typeless language can help, because it's not the type that determines if it's "a square" but the actual properties of the object that determines if it's "a square".
I guess if you want to take the abstraction even further you wouldn't even say you have a quadrilateral but that you have a polygon or even just a shape.
Let's assume we have the class Rectangle with the two (for simplicity public) properties width,height. We can change those two properties: r.width=1, r.height=2. Now we say a Square is_a Rectangle. But though the claim is "a square will behave like a rectangle" we can't set .width=1 and .height=2 on a square object (your class probably adjusts the width if you set the height and vice versa). So there's at least one case where an object of type Square doesn't behave like a Rectangle and therefore you cannot substitute them (completely).
I believe that OOD/OOP techniques exist to enable software to represent the real world. In the real world a square is a rectangle that has equal sides. The square is a square only because it has equal sides, not because it decided to be a square. Therefore, the OO program needs to deal with it. Of course, if the routine instantiating the object wants it to be square, it could specify the length property and the width property as equal to the same amount. If the program using the object needs to know later if it is square, it needs only to ask it. The object could have a read-only Boolean property called “Square”. When the calling routine invokes it, the object can return (Length = Width). Now this can be the case even if the rectangle object is immutable. In addition, if the rectangle is indeed immutable, the value of the Square property can be set in the constructor and be done with it. Why then is this an issue? The LSP requires sub-objects to be immutable to apply and square being a sub-object of a rectangle is often used as an example of its violation. But that doesn’t seem to be good design because when the using routine invokes the object as “objSquare”, must know its inner detail. Wouldn’t it be better if it didn’t care whether the rectangle was square or not? And that would be because the rectangle’s methods would be correct regardless. Is there a better example of when the LSP is violated?
One more question: how is an object made immutable? Is there an “Immutable” property that can be set at instantiation?
I found the answer and it is what I expected. Since I'm a VB .NET developer, that is what I'm interested in. But the concepts are the same across languages. In VB .NET you create immutable classes by making the properties read-only and you use the New constructor to allow the instantiating routine to specify property values when the object is created. You can also use constants for some of the properties and they will always be the same. From creation forward the object is immutable.
发布评论
评论(8)
答案取决于可变性。 如果您的矩形和正方形类是不可变的,那么
Square
实际上是Rectangle
的子类型,并且从第二个派生第一个是完全可以的。 否则,Rectangle
和Square
都可以公开一个没有变异器的IRectangle
,但是从另一个派生一个类型是错误的,因为这两种类型都不是正确的子类型另一个。The answer depends on mutability. If your rectangle and square classes are immutable, then
Square
is really a subtype ofRectangle
and it's perfectly OK to derive first from second. Otherwise,Rectangle
andSquare
could both expose anIRectangle
with no mutators, but deriving one from the other is wrong since neither type is properly a subtype of the other.我相信推理是这样的:
假设你有一个接受矩形并调整其宽度的方法:
考虑到矩形是什么,假设这个测试会通过应该是完全合理的:
...因为改变矩形的宽度不影响其高度。
然而,假设您从 Rectangle 派生了一个新的 Square 类。 根据定义,正方形的高度和宽度始终相等。 让我们再次尝试该测试:
该测试将失败,因为将正方形的宽度设置为 100 也会更改其高度。
因此,从 Rectangle 导出 Square 就违反了里氏替换原则。
“is-a”规则在“现实世界”中有意义(正方形绝对是矩形的一种),但在软件设计的世界中并不总是如此。
编辑
为了回答您的问题,正确的设计可能应该是矩形和正方形都派生自常见的“多边形”或“形状”类,该类不强制执行任何有关宽度或高度的规则。
I believe the reasoning is something like this:
Let's say you have a method that accepts a rectangle and adjusts its width:
It should be perfectly reasonable, given what a rectangle is, to assume that this test would pass:
... because changing a rectangle's width does not affect its height.
However, let's say you've derived a new Square class from Rectangle. By definition, a square has height and width always equal. Let's try that test again:
That test will fail, because setting a square's width to 100 will also change its height.
Thus, Liskov's substitution principle is violated by deriving Square from Rectangle.
The "is-a" rule makes sense in the "real world" (a square is definitely a kind of rectangle), but not always in the world of software design.
Edit
To answer your question, the correct design should probably be that both Rectangle and Square derive from a common "Polygon" or "Shape" class, which does not enforce any rules regarding width or height.
我不同意从矩形导出正方形必然违反 LSP。
在 Matt 的示例中,如果您的代码依赖于宽度和高度独立,那么它实际上违反了 LSP。
然而,如果您可以在代码中的任何地方用矩形替换正方形而不破坏任何假设,那么您就没有违反 LSP。
因此,这实际上取决于抽象矩形在您的解决方案中的含义。
I don't agree that deriving square from rectangle necessarily violates LSP.
In Matt's example, if you have code that relies on width and height being independent, then it does in fact violate LSP.
If however, you can substitute a rectangle for a square everywhere in your code without breaking any assumptions then you're not violating LSP.
So it really comes down to what the abstraction rectangle means in your solution.
我最近一直在为这个问题苦苦挣扎,并认为我应该加入其中:
注意最后一个类,
ResizedRectangle
。 通过将“可调整大小”移至子类中,我们可以在实际改进模型的同时重用代码。 可以这样想:正方形不能在保持正方形的情况下自由调整大小,而非正方形的矩形可以。 但并非所有矩形都可以调整大小,因为正方形就是矩形(并且在保留其“身份”的同时无法自由调整大小)。 (o_O) 因此,创建一个不可调整大小的基Rectangle
类是有意义的,因为这是某些矩形的额外属性。I've been struggling with this problem a lot lately and thought I'd add my hat into the ring:
Notice the last class,
ResizableRectangle
. By moving the "resizableness" into a subclass, we get code re-use while actually improving our model. Think of it like this: a square cannot be freely resized while remaining a square, whereas non-square rectangles can. Not all rectangles can be resized though, since a square is a rectangle (and it cannot be freely resized while retaining its "identity"). (o_O) So it makes sense to make a baseRectangle
class which is not resizable, since this is an extra property of some rectangles.问题在于,所描述的实际上不是一种“类型”,而是一种累积的突现属性。
你真正拥有的只是一个四边形,“方形”和“矩形”都只是从角度和边的属性中衍生出来的产物。
“正方形”(甚至矩形)的整个概念只是对象之间以及相关对象之间的属性集合的抽象表示,而不是对象本身的类型。
这就是在无类型语言的上下文中思考问题会有所帮助的地方,因为不是类型决定它是否是“正方形”,而是对象的实际属性决定它是否是“正方形”。
我想如果你想进一步抽象,你甚至不会说你有一个四边形,而是说你有一个多边形,甚至只是一个形状。
The problem is that what is being described is really not a "type" but an cumulative emergent property.
All you really have is a quadrilateral and that both "squareness" and "rectangleness" are just emergent artifacts derived from properties of the angles and sides.
The entire concept of "Square" (or even rectangle) is just an abstract representation of a collection of properties of the object in relation to each other and the object in question, not the type of object in and of it's self.
This is where thinking of the problem in the context of a typeless language can help, because it's not the type that determines if it's "a square" but the actual properties of the object that determines if it's "a square".
I guess if you want to take the abstraction even further you wouldn't even say you have a quadrilateral but that you have a polygon or even just a shape.
假设我们有 Rectangle 类,它有两个(为了简单起见,公共)属性 width 和 height。 我们可以更改这两个属性:r.width=1,r.height=2。
现在我们说正方形是_矩形。 但是,尽管声明是“正方形的行为就像矩形”,但我们不能在正方形对象上设置 .width=1 和 .height=2 (如果您设置高度,您的类可能会调整宽度,反之亦然)。 因此,至少在一种情况下, Square 类型的对象的行为与 Rectangle 不同,因此您无法(完全)替换它们。
Let's assume we have the class Rectangle with the two (for simplicity public) properties width,height. We can change those two properties: r.width=1, r.height=2.
Now we say a Square is_a Rectangle. But though the claim is "a square will behave like a rectangle" we can't set .width=1 and .height=2 on a square object (your class probably adjusts the width if you set the height and vice versa). So there's at least one case where an object of type Square doesn't behave like a Rectangle and therefore you cannot substitute them (completely).
我相信 OOD/OOP 技术的存在是为了让软件能够代表现实世界。 在现实世界中,正方形是边长相等的矩形。 正方形之所以是正方形,只是因为它有相等的边,而不是因为它决定是正方形。 因此,OO程序需要对其进行处理。 当然,如果实例化对象的例程希望它是正方形,它可以将长度属性和宽度属性指定为等于相同的数量。 如果使用该对象的程序稍后需要知道它是否是正方形,则只需询问它即可。 该对象可以有一个名为“Square”的只读布尔属性。 当调用例程调用它时,该对象可以返回(长度 = 宽度)。 现在,即使矩形对象是不可变的,情况也可能如此。 另外,如果矩形确实是不可变的,则可以在构造函数中设置 Square 属性的值并使用它来完成。 那么为什么这是一个问题呢? LSP 要求子对象在应用时是不可变的,而作为矩形子对象的 square 经常被用作违反其规则的示例。 但这似乎不是一个好的设计,因为当使用例程调用“objSquare”对象时,必须知道其内部细节。 如果它不关心矩形是正方形不是更好吗? 这是因为矩形的方法无论如何都是正确的。 有没有更好的例子来说明何时违反 LSP?
还有一个问题:如何使对象变得不可变? 是否有可以在实例化时设置的“不可变”属性?
我找到了答案,这正是我所期望的。 因为我是一名 VB .NET 开发人员,所以这就是我感兴趣的。但是跨语言的概念是相同的。 在 VB .NET 中,您可以通过将属性设置为只读来创建不可变类,并使用 New 构造函数允许实例化例程在创建对象时指定属性值。 您还可以对某些属性使用常量,它们将始终相同。 从创建开始,对象就是不可变的。
I believe that OOD/OOP techniques exist to enable software to represent the real world. In the real world a square is a rectangle that has equal sides. The square is a square only because it has equal sides, not because it decided to be a square. Therefore, the OO program needs to deal with it. Of course, if the routine instantiating the object wants it to be square, it could specify the length property and the width property as equal to the same amount. If the program using the object needs to know later if it is square, it needs only to ask it. The object could have a read-only Boolean property called “Square”. When the calling routine invokes it, the object can return (Length = Width). Now this can be the case even if the rectangle object is immutable. In addition, if the rectangle is indeed immutable, the value of the Square property can be set in the constructor and be done with it. Why then is this an issue? The LSP requires sub-objects to be immutable to apply and square being a sub-object of a rectangle is often used as an example of its violation. But that doesn’t seem to be good design because when the using routine invokes the object as “objSquare”, must know its inner detail. Wouldn’t it be better if it didn’t care whether the rectangle was square or not? And that would be because the rectangle’s methods would be correct regardless. Is there a better example of when the LSP is violated?
One more question: how is an object made immutable? Is there an “Immutable” property that can be set at instantiation?
I found the answer and it is what I expected. Since I'm a VB .NET developer, that is what I'm interested in. But the concepts are the same across languages. In VB .NET you create immutable classes by making the properties read-only and you use the New constructor to allow the instantiating routine to specify property values when the object is created. You can also use constants for some of the properties and they will always be the same. From creation forward the object is immutable.
它非常简单:)越“基础”的类(派生链中的第一个)应该是最通用的。
例如形状-> 矩形-> 正方形。
这里,正方形是矩形的特殊情况(具有受限尺寸),而矩形是形状的特殊情况。
换句话说 - 使用“is a”测试。 乡绅是一个长方形。 但矩形并不总是正方形。
Its pretty simple :) The more 'base' the class (the first in the derivation chain) should be the most general.
For example shape -> Rectangle -> Square.
Here a square is a special case of a rectangle (with constrained dimensions) and a Rectangle is a special case of a shape.
Said another way - use the "is a" test. A squire is a rectangle. But a rectange is not always a square.