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 originally implemented type Animated, which has a single start method, so this new one will as well.

class fallingObject.rate(yChange: Number) until (stopHeight: Number) -> Animated {

FallingObject has instance variables to hold:

Let's start by looking at the start method.

    method start -> Done {
        animator.while{theObject.y < stopHeight} pausing (pauseTime) do {
           theObject.moveBy(0, yChange)
        } finally {
           theObject.removeFromCanvas
        }
    }

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

How do we modify FallingLeaf (later in that same file) to make use of our new class? First, we have it inherit FallingObject:

class fallingLeaf.atX(x: Number) rate(yChange: Number) until (screenHeight: Number)
                   url(leafURL: String) 
                   on (canvas: DrawingCanvas) -> Animated {
    inherits fallingObject.rate(yChange) until (screenHeight)

FallingLeaf has become a lot simpler. It has a single constant, startingY telling it where to create the leaf image, but it has no more declarations of defs, variables, or methods, not even the start 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:

class fallingLeaf.atX(x: Number) rate(yChange: Number) until (screenHeight: Number)
                   url(leafURL: String) 
                   on (canvas: DrawingCanvas) -> Animated {
    inherits fallingObject.rate(yChange) until (screenHeight)
    def startingY: Number = -60

    theObject := drawableImage.at(x@startingY)size(60,60)
            url(leafURL)on(canvas)
}

By writing inherits fallingObject.rate(yChange) until (screenHeight), the new object gets all of the features of fallingObject, and initializes it use arguments yChange and screenHeight, which were provided by the parameters of fallingLeaf.

Thus a fallingLeaf object is constructing by putting together all defs, vars, and methods from fallingObject and fallingLeaf, running the initialization code for fallingObject, and then running the initialization code from fallingLeaf.

Thus for example, the declaration of theObject in fallingObject is processed before the assignment, which is actually found in fallingLeaf.

When another object sends a method request of start to an object created by fallingLeaf it runs the start code inherited from fallingObject

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

def hailYChange: Number = 10
class hail.atX(x: Number) until (screenHeight: Number)
                   on (canvas: DrawingCanvas) -> Animated {
  inherits fallingObject.rate(hailYChange) until (screenHeight)
  def startingY: Number = -60
  def hailSize: Number = 15

  theObject := filledOval.at (x@startingY) size (hailSize,hailSize) on (canvas)
  theObject.color := white
}

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

Our free 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 inherits fallingObject. So we create fallingLeaf or hail objects.

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

  1. We provide the parameters to the hail class.
  2. It immediately invokes the fallingObject class with the parameters hailYChange (defined right above class hail) and screenHeight. This invokes the initialization code for fallingObject
  3. The hail class then continues with its own initialization code, creating the while FilledOval that represents our hail.

When the start method is requested on the hail object, it will execute the start method in fallingObject because there is no start method in hail.


OverridingTopInheritance definedFallingObject example