Is deriving square from rectangle a violation of Liskov's Substitution Principle? [closed]

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.


I believe the reasoning is something like this:

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.


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.


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.