Is Square Really Not a Rectangle?

llllzllll
2 min readAug 10, 2021

A quote from wiki about Liskov Substitution Principle claims that:

if S is a subtype of T, then objects of type T may be replaced with objects of type S

The most famous and controversial example for this principle states that a square is not a rectangle, because if we have a Rectangle class like the following:

class Rectangle {    float width, height;

public Rectangle(float width, float height) {
this.width = width;
this.height = height;
}
public void setWidth(int a) {width = a;}
public void setHeight(int b) {height = b;}
}class Square extends Rectangle {}

If Square extends this Rectangle class. Then both setWidth and setHeight should change their behavior to change both width and height when either one of them is called. Thus, Rectangle can’t be replaced by the Square, therefore, the conclusion: Square can’t be a subtype of Rectangle!

But apparently, there must be something wrong here, because years of mathematical education taught us that there’s no way that Square can’t be a subtype of Rectangle! So, what could go wrong?

The answer is: Mutability!

If we make the Rectangle immutable, we will no longer have the setter problem! And alternatively, we can change the implementation above to the following:

class Rectangle {    // ....
public static Rectangle factory(float width, float height) {
if (width == height) return new Square(width);
return new Rectangle(width, height);
} public Rectangle setWidth(int a) {
return factory(a, this.height);
}
public Rectangle setHeight(int b) {
return factory(this.width, b);
}
}

It’s totally legit! If a Square object calls a setter and it invalidates the predicate, the factory method will simply create a new Rectangle instance.

Coincidentally, but unsurprisingly, people on this Stackoverflow discussion already found that out.

In addition to giving another example that mutability is evil, here is another takeaway:

A type is not only determined by its fields or abstracted behaviors, it could also depend on the values of the fields and the concrete behaviors of the fields.

In the Square, Rectangle example, Square is a special type for the special case when a Rectangle has equal width and height.

Factory pattern + immutability is a good way to enforce data type matches its values: the factory decides the type given the input, and the immutability guarantees the predicates based on the values never be broken.

Reference

--

--