Is a Square a Rectangle?

In Geometry, a square is a special case of rectangle where the height and width of the shape are equal. When modelling squares and rectangles in object-oriented programming languages, defining this relationship can lead to unexpected bugs.

The "IS A" Relationship

The relationship between a subclass and its base class is often called an "IS A" relationship because the subclass is a version of the superclass. For example, you may develop an application that holds details of team members within an organisation. You may create a TeamMember class that is the base type for the SoftwareDeveloper class. As you can substitute SoftwareDeveloper objects where a TeamMember is expected, you can say that a SoftwareDeveloper is a TeamMember.

The concept of the "IS A" relationship can be misleading. When determining if one type should inherit from another, it is important to remember that you are dealing with representations of real-world objects and not the objects themselves. In the above relationship, a physical programmer is a physical team member. The behaviour of a programmer object is also an extension of the behaviour of a team member. An inheritance relationship is therefore suitable. There are many situations where the relationship between two physical objects suits an "IS A" relationship but where their behaviours do not.

Squares and Rectangles

One of the situations where the relationship between physical objects and their representations differs is the relationship between squares and rectangles. Geometrically, a square is a rectangle. Both meet the requirements of a rectangle; they are four-sided shapes where all corners are right-angles. Squares have an additional property that all sides are the same length.

The physical "IS A" relationship between a square and a rectangle may lead you to develop a class hierarchy where Square is a subclass of Rectangle. You may reason that the physical relationship can be mapped directly to an inheritance relationship in code. This is not the case because of some subtle but significant problems relating to the SOLID principles, most notably the Liskov Substitution Principle.

We can demonstrate the flawed inheritance relationship by creating Rectangle and Square classes in C# and seeing where problems arise. The example code will be simplistic to show that problems start to occur quickly. They would worsen as further functionality was added. To begin, create the Rectangle class with properties for Height and Width and a method to calculate the shape's area:

The above Square type is not ideal as it allows squares to be defined without a matching height and width. Currently Square is nothing more than a Rectangle with a new name. One way to update the class, to ensure that represented objects are indeed square, is to override the property setters. When one dimension is set, the other could be updated to match. The revised code is shown below:

The Problems

When considered in isolation the Square class now functions correctly. It is not possible to create a Square object with mismatched dimensions. However, we have violated the Liskov Substitution Principle (LSP) and potentially introduced bugs into clients of the Rectangle class. The most obvious violation of the LSP is a problem with invariants. Clients of the Rectangle class know that when the Height property is changed the Width property is invariant. With the introduction of the Square class, this rule has been broken.

Consider the following code. Two Rectangle objects are created and their heights and widths are set. In the first case the height is set before the width and in the second, the width is set first. This does not matter as both properties are invariant when the other is changed. The last line of code compares the two rectangles and determines that they have matching dimensions.

The LSP states that substituting an object of a subclass should not change the behaviour, or the correctness, of the program. However, if we substitute squares for rectangles the result of the comparison is false. This is because changing one Square dimension affects the other and the order in which the width and height are specified has become important:

This seemingly innocuous inheritance relationship has caused problems whilst Rectangle and Square are still simple classes. As their functionality is extended further, the problems may be compounded. For example, the classes may be used to represent shapes in a computer-aided design (CAD) package. This software may require that a rectangle be stretched to fit within a given space whilst retaining its aspect ratio. Substituting a square might cause the stretching to produce a square that is too large or small. If the package allowed a fixed value to be added to the height and width, squares may incorrectly have this value added twice.

The solution to the problem is not to include the inheritance relationship. An alternative may be to have a "Shape" base class or an "IShape" interface that is shared by rectangles and squares. The base class would not include properties for Height and Width, instead allowing the dimensions of different shapes to be handled by their implementations. A Rectangle subclass would have Height and Width properties and a Square may have a single Size value. Further classes, such as Pentagon, may be defined using multiple length and angle properties.

Another alternative to the Square and Rectangle problem may be to remove the Square class altogether. The above Square type does not extend the Rectangle by adding new properties or methods so may be entirely unnecessary. Instead, you could simply use rectangles where the dimensions happen to be equal.

Conclusion

The Liskov Substitution Principle tells us that inheritance relationships should be based upon the external behaviour of types and not on the characteristics of the real-world objects that they may represent. Although a square is a rectangle, the external behaviour of the two representations is incompatible, so inheritance is invalid. This problem applies to squares and rectangles, circles and ellipses and many other physical objects that you may wish to model. Inheritance is a powerful tool for the object-oriented developer but careful thought must be given to external behaviour before applying it.