OverridingTopInheritance definedFallingObject example

FallingObject example

In general, when we see classes that have common behaviors, we should consider creating a class to hold the common behavior, which we can then extend to the subclasses that define specific behavior. For example, we've seen several applications that involve moving objects, and in each there is some similar animation code.

For example, think about the leaves from the falling leaves example and the ball from our early "Pong" examples.

Demo: Falling Leaves

Demo: Falling Ball

What do they have in common?

What is different about them?

Let's create a class, FallingObject, that captures the common features. When we create a FallingObject, we need to decide where it should fall to, what it should look like, and how fast it should fall. The constructor creates the FallingObject with these properties. The run method causes the object to fall at the specified speed and then hides it at the end.

Demo: Falling Leaves, etc. using the FallingObject

Now, let's look at the code. First, let's look at FallingObject. Both the leaves and the falling ball were originally ActiveObjects, so our new class, FallingObject will extend ActiveObject:

public class FallingObject extends ActiveObject

FallingObject has instance variables to hold:

Let's start by looking at the run method.

    public void run() {

        while (object.getY() < fallToPos) {

            pause(DELAY_TIME);
            object.move(0, ySpeed);
        }
        object.removeFromCanvas();
    }

We animate the object down the screen until it hits the final position, then we remove it from the canvas.

How do we modify FallingLeaf to make use of our new class? First, we have it extend FallingObject instead of ActiveObject:

    public class FallingLeaf extends FallingObject

FallingLeaf has become a lot simpler. It no longer has any instance variables or a run method. It inherits all of these from the FallingObject class.

The only thing that distinguishes a leaf from any other falling object is how it is created:

    public FallingLeaf(DrawingCanvas canvas, Image leafPic, 
                       double x, double aSpeed, 
                       int aScreenHeight) {
        // first, call "up" to the constructor of the FallingObject class
        super(aScreenHeight, aSpeed);

        // create our leaf, but use the instance variable given in
        // the FallingObject instead
        object = new VisibleImage(leafPic, 0, 0, canvas);
        object.move(x, - object.getHeight());
        
        start();
    }

The constructor is responsible for creating a generic falling object and then customizing it, in this case to be a leaf. The generic falling object is created by the super call. super is a keyword that tells Java to call the constructor of the superclass, in this case FallingObject, passing it the height at which to disappear and its speed. The FallingObject constructor just saves these in instance variables:

    public FallingObject(int thePos, double theSpeed) {

        ySpeed = theSpeed;
        fallToPos = thePos;

        // note that it is the responsibility of the derived class
        // to call start();
    }

Next the FallingLeaf constructor creates the leaf-specific parts, then calls start to start the falling.

Given this, how can we create a falling ball? We can take advantage of our FallingObject class to get the instance variables and run method, and we just need to provide a constructor. Since we're putting this into the falling leaf example, we'll call our "falling ball" Hail

public class Hail extends FallingObject {
        
    // size of a hail pellet
    private static final int SLEET_SIZE = 10;

    // color for sleet
    private static final Color SLEET_COLOR = new Color(220,220,255);

    // initialize the instance variables and start the active object
    public Hail(DrawingCanvas canvas, double x, double aSpeed, 
                       int aScreenHeight) {
        // first, call "up" to the constructor of the FallingObject class
        super(aScreenHeight, aSpeed);

        // create our hail pellet
        object = new FilledOval(0,0,SLEET_SIZE,SLEET_SIZE,canvas);
        object.setColor(SLEET_COLOR);
        object.move(x, - object.getHeight());
        
        start();
    }
}

These classes are both a lot simpler because we are reusing instance variables and methods from the FallingObject class.

Our Tree doesn't create FallingObjects, as those have no visual representation, and in fact doing so would lead to errors, since no object is created unless we do so in our constructor in the class that extends FallingObject. So we create FallingLeaf or FallingSleet objects.

Let's think about what happens when we construct a Hail:

  1. We call the Hail constructor.
  2. It immediately calls the FallingObject constructor (the super call).
  3. The Hail constructor then continues with its own code, creating the FilledOval that represents our sleet.
  4. The Hail constructor calls start. start is not defined locally so it must be inherited. Java looks for it in FallingObject, but it is not there, so it looks up the chain of class extensions. It is defined in ActiveObject.
  5. start does some work to manage the new concurrent thread and then calls the run method. Hail does not define run locally, but it does inherit it from FallingObject so the run method in FallingObject is executed.

OverridingTopInheritance definedFallingObject example