Inheritance and Polymorphism

Object oriented programming is one of the dominant programming paradigms today. It is characterized by many buzzwords, but inheritance and polymorphism are two of the most important.

Motivation

Suppose we want to write an application that manages shapes that has these requirements

Writing just one class for all shapes gets very messy and causes headaches when we have to add new kinds of shapes.

If we write completely separate classes for circles and squares, then how can we take advantage of similarities? For example methods getColor() and setColor() should be the same for all shapes.

In UML, we can capture the similarities and differences like this:

figures1uml.png

From this diagram we see

Exercise: Add a Polygon class to this hierarchy.

A Simple Java Example

A straightforward implementation of the above diagram looks like:

Figure.java
import java.awt.Color;

/**
 * A little library of figures. It is the classic example of object oriented
 * programming. In our library of figures all figures are derived from an
 * abstract class called Figure. It is abstract because there are no plain
 * figures; all real figures are instances of classes derived from Figure. A
 * figure has a color which can be read and written with getColor() and
 * setColor(); the default color is blue. Also, you can get the area and
 * perimiter of any figure; these methods are abstract because their
 * implementation depends on the particular subclass.
 */
public abstract class Figure {
    protected Color color = Color.blue;

    public void setColor(Color c) {
        color = c;
    }

    public Color getColor() {
        return color;
    }

    public abstract double perimeter();

    public abstract double area();
}

Circle.java
import java.awt.Point;

/**
 * A simple circle class which is part of our figures package.
 */
public class Circle extends Figure {
    protected Point center;
    protected double radius;

    public Circle(Point c, double r) {
        center = c;
        radius = r;
    }

    public double perimeter() {
        return 2.0 * Math.PI * radius;
    }

    public double area() {
        return Math.PI * radius * radius;
    }

    public void expand(double scaleFactor) {
        radius *= scaleFactor;
    }
}

Square.java
import java.awt.Point;

/**
 * A simple square class which is part of our figures package.
 */
public class Square extends Figure {
    protected Point upperLeft;
    protected double sideLength;

    public Square(Point upperLeft, double sideLength) {
        this.upperLeft = upperLeft;
        this.sideLength = sideLength;
    }

    public double perimeter() {
        return 4 * sideLength;
    }

    public double area() {
        return sideLength * sideLength;
    }
}
Exercise: These example classes aren't really world-class; we should have getters and setters in the subclasses. Tighten up these classes. If you really are a world-class developer, add equals, hashCode, and toString.
Exercise: Implement a Polygon class.

Here is a throwaway application that shows off late-binding, which is a kind of polymorphism. We say the call f.area() within main() is late-binding because the actual area() method called is not known at compile time. In fact at run-time, sometimes that call refers to Square.area() and sometimes it refers to Circle.area().

SimpleFigureDemo.java
import java.awt.Color;
import java.awt.Point;

/**
 * An application class that simply illustrates polymorphism.
 */
public class SimpleFigureDemo {

    /**
     * Create an array of various different kinds of figures, an mess around with
     * them by setting their colors and printing their areas.
     */
    public static void main(String[] args) {
        Figure[] myFigures = new Figure[] { new Circle(new Point(50, 50), 40), new Circle(new Point(100, 200), 17),
                new Square(new Point(90, 143), 35), new Circle(new Point(50, 90), 20),
                new Square(new Point(0, 0), 15) };

        for (Figure f : myFigures) {
            f.setColor(Color.red);
            System.out.println(f.area());
        }
    }
}

Note that one strong advantage of object-orientation is that new kinds of figure classes can be written at a later date, and the for-loop of main() method will still work without modification of any kind! Also, the other figure classes would not have to be changed either. This would definitely NOT be the case in primitive languages such as Pascal and C, where some technique like unions or variant records would be used, and addition of new kinds of objects would require that source code be messed with.

Another Java Example

Here is another example. First, the abstract class:

Progression.java
/**
 * Abstract class for progressions. A progression is something you create to
 * generate values in some sequence. Calling next() returns the next value in
 * the progression; it is an abstract method because its operation depends on
 * the particular kind of progression you have. Calling next(n) returns the
 * value n steps away; it is not abstract because it works the same regardless
 * of the kind of progression you have.
 */
public abstract class Progression {

    /**
     * The place to store the next value to return from next()
     */
    protected int nextValue;

    public Progression() {
        nextValue = 0;
    }

    public Progression(int initialValue) {
        nextValue = initialValue;
    }

    public int next() {
        int valueToReturn = nextValue;
        advance();
        return valueToReturn;
    }

    public abstract void advance();
}

and three different implementations:

ArithmeticProgression.java
/**
 * An ArithmeticProgression is a progression x(0), x(1), x(2), ... such that for
 * each x(i), x(i+1) = x(i) + inc for some increment inc which is set at the
 * time that the progression was created.
 */
public class ArithmeticProgression extends Progression {

    protected int increment;

    public ArithmeticProgression(int start, int increment) {
        super(start);
        this.increment = increment;
    }

    public void advance() {
        nextValue += increment;
    }
}

GeometricProgression.java
/**
 * An ArithmeticProgression is a progression x(0), x(1), x(2), ... such that for
 * each x(i), x(i+1) = x(i) * b for some base b which is set at the time that
 * the progression was created.
 */
public class GeometricProgression extends Progression {
    protected int base;

    public GeometricProgression(int start, int base) {
        super(start);
        this.base = base;
    }

    public void advance() {
        nextValue *= base;
    }
}

FibonacciProgression.java
/**
 * A FibonacciProgression is the progression 0, 1, 1, 2, 3, 5, 8, 13, 21, 34,
 * 55, 89, 144, ..., that is, x(0), x(1), x(2), ... such that for each x(i),
 * x(i+2) = x(i) + x(i+1).
 */
public class FibonacciProgression extends Progression {

    protected int previous;

    public FibonacciProgression() {
        super(0);
        previous = 1; // hack to make it work :-)
    }

    public void advance() {
        int oldPreviousValue = previous;
        previous = nextValue;
        nextValue += oldPreviousValue;
    }
}

Note the design of the progression class. Someone wanting to make a new kind of progression simply encodes the basic rule for getting from one value to the next. The means for returning the value to the client, i.e., the method next(), is built in to the superclass. This is a common theme in good software design. You define a mechanism only once. If our design required every subclass implementor to repeat the same code over and over, somewhere an annoying typo would find its way in.

Exercise: Draw the UML diagram for this collection of classes.

A demo program:

ProgressionDemo.java
/**
 * Illustrates different progressions in action.
 */
public class ProgressionDemo {

    /**
     * Defines a few progression objects of different progression classes and prints
     * the first 20 values of each to System.out. It is simply meant as an
     * introductory example of polymorphism.
     */
    public static void main(String[] args) {
        showInitialSequence(new ArithmeticProgression(10, 4));
        showInitialSequence(new ArithmeticProgression(-25, 5));
        showInitialSequence(new GeometricProgression(1, 2));
        showInitialSequence(new FibonacciProgression());
    }

    /**
     * Displays the first 20 values from the given progression.
     */
    public static void showInitialSequence(Progression p) {
        System.out.print("( ");
        for (int i = 0; i < 20; i++) {
            System.out.print(p.next() + " ");
        }
        System.out.println(")");
    }
}

Note that the method showInitialSequence() never has to be changed even if a thousand new progression subclasses are defined later.