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 snowflake and the falling ball were originally ActiveObjects, so our new class, FallingObject will extend ActiveObject as well.
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 FallingSnow 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 falling hail? 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. (See the code on-line)
These classes are both a lot simpler because we are reusing instance variables and methods from the FallingObject class.
Our Cloud 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 Hail objects.
Let's think about what happens when we construct a Hail:
FallingObject example |