This post originated from an RSS feed registered with Java Buzz
by Elliotte Rusty Harold.
Original Post: A Square Is Not a Rectangle
Feed Title: The Cafes
Feed URL: http://cafe.elharo.com/feed/atom/?
Feed Description: Longer than a blog; shorter than a book
The following example, taken from an introductory text in object oriented programming, demonstrates a common flaw in object oriented design. Can you spot it?
public class Rectangle {
private double width;
private double height;
public void setWidth(double width) {
this.width = width;
}
public void setHeight(double height) {
this.height = height;
}
public void getHeight() {
return this.height;
}
public void getWidth() {
return this.width;
}
public double getPerimeter() {
return 2*width + 2*height;
}
public double getArea() {
return width * height;
}
}
public class Square extends Rectangle {
public void setSide(double size) {
setWidth(size);
setHeight(size);
}
}
(I’ve changed the language and rewritten the code to protect the guilty.)
There are actually several problems here. Thread safety is one, but let’s assume the class doesn’t need to be thread-safe. Another is that it’s possible to give the sides negative lengths. That we could fix with a couple of judiciously thrown IllegalArgumentExceptions. However the problem that most troubles me is demonstrated here:
Square s = new Square();
s.setSide(5.0);
s.setHeight(10.0);
In object oriented programming, it is necessary that a subclass be able to fulfill the contract of its superclass. In this case, that means the square has to respond to setHeight() and setWidth() calls. However doing so enables the square to violate the nature of a square. A square cannot stand in for a rectangle.
You can try to work around this by overriding the setHeight() and setWidth() methods. For example, one might call the other:
public class Square extends Rectangle {
public void setWidth(double width) {
super.setWidth(width);
super.setHeight(height);
}
public void setHeight(double height) {
super.setWidth(width);
super.setHeight(height);
}
public void setSide(double size) {
super.setWidth(width);
super.setHeight(height);
}
}
However this is fundamentally unsatisfying because there is no reasonable expectation that calling one of setHeight() on a Rectangle object will also invoke the setWidth() method or vice versa. The contract of the Rectangle class is that you can set the width and the height independently, and the Square subclass violates that. Setting width when you’re setting height is an unexpected side effect. It violates the single responsibility principle1.
We could instead just forbid setHeight() and setWidth() completely by throwing UnsupportedOperationException:
public class Square extends Rectangle {
public void setWidth(double width) {
throw new UnsupportedOperationException();
}
public void setHeight(double height) {
throw new UnsupportedOperationException();
}
public void setSide(double size) {
super.setWidth(width);
super.setHeight(height);
}
}
However, this is really just a louder way of warning the client that the Square class does not fulfill the contract of the Rectangle class. It doesn’t address the fundamental problem that, in object oriented terms, a square is not a rectangle. The geometric nature of a square is incompatible with the object-oriented definition of a rectangle given above.
There is, however, a way out of this conundrum. Our problem only arises because of the setter methods. If constructor were used instead, and no setters were exposed, then it would be possible to make a square a subclass of rectangle without violating any contracts. For example,
public class Rectangle {
private double width;
private double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public void getHeight() {
return this.height;
}
public void getWidth() {
return this.width;
}
public double getPerimeter() {
return 2*width + 2*height;
}
public double getArea() {
return width * height;
}
}
public class Square extends Rectangle {
public Square(double size) {
super(size, size);
}
}
As long as the Rectangle class is immutable, we can define subclasses that are limited to a particular subset of rectangles, such as squares. This is one more reason to prefer immutability. To the extent possible, define the public interface in terms of what an object is and what it does rather than what you can do to it. However that’s not always possible, and in those cases you need to be extremely careful around inheritance. Otherwise constraints can be violated when you least expect, thus introducing subtle and potentially dangerous bugs in your code.
1 Actually the single responsibility principle is usually understood to apply to classes, but it’s even more critical that it apply to methods. each method should do exactly one thing and one thing only. Side effects should be avoided.