Aside — Careful
Warning
Don’t think of objects as their real world counterparts — that’s a fallacy that OOP promised but failed to deliver.
 
- There is nothing wrong with extending concrete classes, but this is where things can become problematic 
- Sometimes taking literal inspiration can be bad 
Rectangles and Squares
- A classic example used for teaching inheritance is squares and rectangle 
 
 
- In reality, a square is a special type a rectangle - A square is a rectangle where the length and width are equal 
 
- Below is a simple - Rectangleclass
 1public class Rectangle {
 2
 3    private double length;
 4    private double width;
 5
 6    public Rectangle() {
 7        this(1,1);
 8    }
 9
10    public Rectangle(double length, double width) {
11        this.length = length;
12        this.width = width;
13    }
14
15    public double getLength() {
16        return length;
17    }
18
19    public void setLength(double length) {
20        this.length = length;
21    }
22
23    public double getWidth() {
24        return width;
25    }
26
27    public void setWidth(double width) {
28        this.width = width;
29    }
30
31    public double getArea() {
32        return length * width;
33    }
34
35    public String toString() {
36        return String.format("Rectangle(Length = %.2f, Width = %.2f)", length, width);
37    }
38}
- One can also make a - Squareclass that extends the- Rectangleclass
 1public class Square extends Rectangle {
 2
 3    public Square() {
 4        // Call the superclass' constructor
 5        super();
 6    }
 7
 8    public Square(double side) {
 9        // Call the superclass' constructor
10        super(side, side);
11    }
12
13    public double getSide() {
14        // Could have done getLength
15        return getWidth();
16    }
17
18    public void setSide(double width) {
19        setWidth(width);
20    }
21
22    public String toString() {
23        return String.format("Square(Side = %.2f)", getSide());
24    }
25}
- This seems great 
- Inherit - getLength,- getWidth, and- getArea
- Override - toStringfor specific requirements
- Hijack the superclass’ constructors with - super()in a similar way to using- this()
Liskov’s Substitution Principle
- 
- This is the “L” in the SOLID design principals 
 
1public void doubleArea(Rectangle rect) {
2    rect.setWidth(2.0 * rect.getWidth());
3}
- doubleAreais a method that will double the area of a- Rectangle
- Think of what will happen when running this 
1Square mySquare = new Square(10);
2doubleArea(mySquare);
- Since - Squareinherits from- Rectangle,- setWidthexists
- But this code will cause the - Squareto have an unequal length and width- Thus, the - Squareis no longer a square
 
- This can be fixed by overriding the - setWidth(and- setLength) methods in the- Squareclass
 1    // Add to Square class to override
 2    // Rectangle's setters
 3    public void setWidth(double width) {
 4        super.setWidth(width);
 5        super.setLength(width);
 6    }
 7
 8    public void setLength(double length) {
 9        this.setWidth(length);
10    }
- What happens now if we call this? 
1Square mySquare = new Square(10);
2doubleArea(mySquare);
- Now the length and width are equal 
- But this will cause the - Squareto not double in size, but quadruple, which is a problem
- This means it is not possible to substitute the - Rectanglefor a- Squarefor- doubleArea
- However, this can be fixed by changing the - doubleAreamethod
1public void doubleArea(Rectangle rect) {
2    if (rect instanceof Square) {
3        rect.setWidth(Math.sqrt(2.0) * rect.getWidth());
4    } else {
5        rect.setWidth(2.0 * rect.getWidth());
6    }
7}
- Now this solves it 
- Except, Hyrum’s Law says that all observable behaviours, intentional or not, will be depended on by somebody 
- So, someone out there depends on the fact that - doubleAreais quadrupling the- Square, even though it honestly shouldn’t be
- Maybe this can be fixed by adding another method and changing - doubleAreaback for the person depending on the problematic functionality
 1public void doubleArea(Rectangle rect) {
 2    rect.setWidth(2.0 * rect.getWidth());
 3}
 4
 5public void myDoubleArea(Rectangle rect) {
 6    if (rect instanceof Square) {
 7        rect.setWidth(Math.sqrt(2.0) * rect.getWidth());
 8    } else {
 9        doubleArea(rect);
10    }
11}
- Now this solves it 
- But, now there is a method saying: if it’s a - Squaredo one thing, if it’s a- Rectangledo another thing- So… it would seem that here, a - Squareis not a- Rectangle
 
- There is also two pieces of code trying to do the same thing - What happens if - Rectanglegets extended again?
- Write another version of the method? 
 
- This ended up requiring a lot of extra work for no reason at all - The code got more complex 
 
- It’s going to be a lot easier to just not use inheritance here 
- If one is truly set on reusing the code, then the better idea here is composition over inheritance - Have the - Squareuse an internal instance of a- Rectangleto get the desired functionality from- Rectangle