Does a square draw from a rectangle in violation of the Liskov Substitution Principle?

I am new to the development and study of design principles.

It is said that deducing a square from a rectangle is a classic example of a violation of the Liskov substitution principle.

If so, what should be the right design?

+51
design oop lsp
Jun 23 '09 at 3:39
source share
8 answers

I believe the argument is as follows:

Let's say you have a method that takes a rectangle and adjusts its width:

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

It should be perfectly reasonable, considering what a rectangle is, to assume that this test passes:

 Rectangle rect = new Rectangle(50, 20); // width, height SetWidth(rect, 100); Assert.AreEqual(20, rect.Height); 

... because changing the width of the rectangle does not affect its height.

However, let's say you got a new square class from Rectangle. By definition, a square has a height and a width that are always equal. Try again:

 Rectangle rect = new Square(20); // both width and height SetWidth(rect, 100); Assert.AreEqual(20, rect.Height); 

This test will fail because setting the square width to 100 will also change its height.

Thus, the Liskov substitution principle is violated by deducing the square from the rectangle.

The "is-a" rule makes sense in the "real world" (a square is definitely a kind of rectangle), but not always in the software development world.

Edit

To answer your question, the correct design should probably be that Rectangle and Square are derived from the general class "Polygon" or "Shape", which does not apply any rules regarding width or height.

+53
Jun 23 '09 at 3:54
source share

The answer depends on the variability. If your rectangles and square classes are immutable, then Square really a subtype of Rectangle , and it is great for getting the first of the second. Otherwise, Rectangle and Square could expose IRectangle without mutators, but the conclusion of one of the other is incorrect, since none of them is the corresponding subtype of the other.

+63
Jun 23 '09 at 4:00
source share

I do not agree that the conclusion of a square from a rectangle necessarily violates the LSP.

In the example with Matt, if you have code that depends on the width and height independently, then it actually violates the LSP.

If, however, you can replace the rectangle with a square everywhere in your code without violating any assumptions, then you are not violating the LSP.

So, it really comes down to what the abstraction rectangle means in your solution.

+5
Jun 23 '09 at 4:11
source share

Recently, I have been struggling a lot with this problem and thought that I would add a hat to 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; } } 

Pay attention to the last class, ResizableRectangle . By moving "resizableness" to a subclass, we get code reuse, actually improving our model. Think of it this way: a square cannot be freely resized to remain a square, while rectangles are not square. Not all rectangles can be resized because the square is a rectangle (and it cannot be freely resized, while preserving its “identity”). (o_O) Thus, it makes sense to create a base class Rectangle , which cannot be changed, since this is an additional property of some rectangles.

+3
Aug 13 '15 at 8:38
source share

Suppose we have a Rectangle class with two (for simplicity, public) width, height properties. We can change these two properties: r.width = 1, r.height = 2.
Now we say that the square is_a Rectangle. But although the claim “the square will behave like a rectangle”, we cannot set .width = 1 and .height = 2 to a square object (your class will probably adjust the width if you set the height and vice versa). So, there is at least one case where an object of type Square does not behave like a rectangle, and therefore you cannot replace it (completely).

+2
Jun 23 '09 at 3:53
source share

I believe that OOD / OOP methods exist that allow software to represent the real world. In the real world, a square is a rectangle with equal sides. A square is a square only because it has equal sides, and not because it decided to be a square. Therefore, the OO program must handle this. Of course, if the routine that creates the object wants it to be square, it could specify the length property and the width property equal to the same amount. If the program using the object needs to know later, if it is square, you only need to ask. An object may have a read-only logical property called Square. When the calling procedure calls it, the object may return (Length = Width). Now this can be the case even if the rectangle object is immutable. In addition, if the rectangle is really unchanged, the value of the Square property can be set in the constructor and executed with it. Why is this a problem? LSP requires that the sub-objects be immutable for use, and the square is a sub-object of the rectangle is often used as an example of its violation. But this does not seem like a good design, because when the procedure used calls the object as "objSquare", it must know its internal detail. Wouldn't it be better if it doesn’t care if the rectangle was square or not? And that would be because the rectangle methods would be correct independently. Is there a better example of when an LSP is violated?

Another question: how is an object immutable? Is there an “Immutable” property that can be set when an instance is created?

I found the answer, and this is what I expected. Since I'm a VB.NET developer, this is what interests me. But the concepts are the same in different languages. In VB.NET, you create immutable classes by creating read-only read-only properties, and you use the New constructor to allow an instance routine to set property values ​​when creating an object. You can also use constants for some properties, and they will always be the same. From creation forward, the object is immutable.

+1
May 11 '10 at 23:47
source share

The problem is that what is being described is not really a "type", but a cumulative property that appears.

All you have is a quadrangle and that “rectangularity” and “rectangularity” are just arising artifacts derived from the properties of angles and sides.

The whole concept of “Square” (or even a rectangle) is simply an abstract representation of the set of properties of an object in relation to each other and the object in question, and not to the type of object inside and out of it.

This can help thinking of the problem in the context of an impersonal language, because it is not the type that determines whether it is a “square”, but the actual properties of an object that determines whether it is a “square”.

I think if you want to abstract away even more, you won’t even say that you have a quadrangle, but you have a polygon or even just a shape.

0
Aug 14 '13 at 17:14
source share

Its quite simple :) The larger the "base" 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 limited dimensions), and a rectangle is a special case of shape.

Speak in a different way - use the "eat" test. A squire is a rectangle. But a rectangle is not always a square.

-3
Jun 23 '09 at 3:44
source share



All Articles