Creating and Using Jeroo Methods

For some problems, it would be convenient if we could extend the basic behaviors of Jeroos (or other objects). Java allows us to write programmer-defined methods that extend the behavior of every object created from a given class.

The concepts of behavior and method were defined in Chapter 1 and are repeated here. A behavior is an action that an object can take or a task that it can perform in response to a request from an external source. A method is a collection of statements that are written in some programming language to describe a specific behavior.

These definitions imply that the creation of a method is a two-part process. First, we need to define and name the new behavior. Second, we need to write the source code for the method.

Defining a Behavior

The first question we must ask is "How do I decide on a good behavior?" There is no fixed answer to this question, but there are some guidelines to follow.

  1. Examine the high-level algorithm. Any complex, but well-defined, step is a candidate for a new behavior, especially if two or more Jeroos need to perform that step.

  2. Examine the detailed-algorithm. Any sequence of steps that occur several times is a candidate for a new behavior.

These guidelines serve as a starting point, but experience is a good teacher. Examine your own programs and those of others. A good behavior has a very clear definition and is used more than once in the program.

Writing a Jeroo Method

A Jeroo method contains the source code that describes what an arbitrary Jeroo needs to do to carry out the corresponding behavior. The form of a Jeroo method is:

The structure of a method.

The methodIdentifier on the first line (the header line) is a name that the programmer chooses for the method. The name should indicate the corresponding behavior. The rules for creating an identifier for a method are the same as those given in Chapter 2--but remember that we always start method names with a lowercase letter. In every method, we should indent every line between the opening and closing braces.

The name of a method should be a verb or a short verb phrase that describes what the method does.

Since a Jeroo method defines a behavior that applies to every Jeroo, we cannot send a message to a specific Jeroo. Instead, we use the special Java name this, which is like a pronoun that refers to the Jeroo that is performing the entire method.

Example: Turn Around

If we wanted to add a method to cause a Jeroo to turn around, we need a class to place it in. We have to create our own subclass of Jeroo to hold our code. In Greenfoot4Sofia, you can right-click on the Jeroo class and create a new subclass with a name of your own choosing. In that new subclass, you could add a method to turn the jeroo around:

// ----------------------------------------------------------
/**
 * Turn the jeroo around 180 degrees so it faces the opposite
 * direction.
 */
public void turnAround()
{
    this.turn(LEFT);
    this.turn(LEFT);
}

Example: One Method Can Use Another, or Even Itself

This example introduces two new behaviors: planting four flowers in a row, and planting two adjacent rows with four flowers per row.

// ----------------------------------------------------------
/**
 * Plant four flowers in a row, starting at the current location.
 */
public void plantFour()
{
   this.plant();   // -- one ---

   this.hop();
   this.plant();   // -- two ---

   this.hop();
   this.plant();   // -- three ---


   this.hop();
   this.plant();   // -- four ---
}


// ----------------------------------------------------------
/**
 * Plant two adjacent rows of flowers.
 */
public void plantRowsOfFour()
{
   // --- Plant first row ---
   this.plantFour();

   // --- Move into position for next row ---
   this.turn(RIGHT);
   this.hop();
   this.turn(RIGHT);

   // --- Plant second row (in opposite direction) ---
   this.plantFour();
}

Using a Jeroo Method

A Jeroo method is used just like any other method. In our island's myProgram() method, we just have to be sure to create a jeroo from our special subclass that contains the new methods we want to use. Then we send a message to a specific Jeroo object, requesting that Jeroo to perform the task associated with the method.

As an example, suppose we had created our own Jeroo subclass called PlantingJeroo, and added the plantFour() and plantRowsOfFour() methods to it. Then in our island subclass, we could have a new Jeroo named Ali plant two rows of flowers, south and east of (5, 5):

public void myProgram()
{
    PlantingJeroo ali = new PlantingJeroo(5, 5, 8);
    this.add(ali);

    ali.plantRowsOfFour();
}

A Word About Constructors

We know that when we create a subclass that it inherits all of the methods and attributes from the class that it extends. If you create a subclass of Jeroo called PlantingJeroo, then any PlantingJeroo object can perform all of the methods that any Jeroo knows--because a PlantingJeroo is a special kind of Jeroo. The PlantingJeroo class inherits all of the methods and attributes from the class Jeroo, and also understands any new ones you write, such as the platRowsOfFour() method. Computer scientists sometimes call this an is-a relationship, because every PlantingJeroo object is a Jeroo at the same time--just a Jeroo that can do more.

An is-a relationship exists between a subclass and its superclass, since every instance of the subclass is also an instance of the superclass at the same time.

Also, as we have already read in Chapter 3, a constructor is a special kind of method that is used to initialize a brand new object. But, while a subclass automatically inherits all of the (plain) methods and attributes from its superclass, it does not inherit constructors. That means that the object instantiation for Ali in the previous example will not actually compile--unless we provide an appropriate constructor for our PlantingJeroo subclass.

One reason that subclasses do not automatically inherit constructors is because subclasses can add new attributes in addition to new methods, and those attributes must be initialized, no matter what. But any constructor from a superclass won't know anything about the subclass' new attributes and can't initialize them appropriately. So subclasses have to explicitly define every constructor they support, all the time.

Every time you create a subclass, you are responsible for defining all of the constructors it supports. Constructors are not inherited from superclasses.

Fortunately, while constructors are not inherited, there is a simple pattern for defining them. In our PlantingJeroo, we can add the following constructor:

// ----------------------------------------------------------
/**
 * Create a new Jeroo facing east.
 * @param x         The x-coordinate of the Jeroo's location.
 * @param y         The y-coordinate of the Jeroo's location.
 * @param flowers   The number of flowers the Jeroo is holding.
 */
public PlantingJeroo(int x, int y, int flowers)
{
    super(x, y, flowers);
}

While we have not yet covered all of the features in this small piece of code, the gist is straightforward. A constructor is declared like a regular method, except that we omit the word void and its name is exactly the same as the class name. Here, we are defining a constructor for our PlantingJeroo subclass that takes three numbers (integers) as arguments, representing the x and y coordinates of the Jeroo's location and the number of flowers in its pouch.

The body of this constructor contains only a single line that uses the special Java keyword super. This word can only be used as the first word inside a subclass constructor, and it allows us to invoke a superclass constructor, passing it any information it might need. So here, we are saying that the first (and only) action in our PlantingJeroo constructor is to call the constructor for its superclass (Jeroo), passing the x and y coordinates and number of flowers. This allows the superclass to initialize all of its attributes correctly with the given information. If our subclass needed more initialization, we would perform that in following statements in the subclass constructor's body.

But for now, this constructor is enough for our PlantingJeroo class. It will allow us to create a PlantingJeroo object by specifying its location and number of flowers. That will in turn allow us to instantiate the Ali Jeroo in the previous example without problems.

We should always define a behavior carefully before we write the code for the corresponding method. A complete definition for a behavior must include a statement of the preconditions and the postconditions.

A precondition for a method is something that is assumed to be true before the method is invoked. The portion of the code that invokes the method is responsible for ensuring that all preconditions are satisfied before the method is invoked.

A postcondition for a method is something that is true after the method has been executed. The code within the method is responsible for ensuring that all postconditions are met.

The process of determining good preconditions and postconditions can be difficult, but it is easier if we remember a few characteristics of objects and methods.

  1. All work is done by sending messages to objects.

  2. Exactly one object executes a method in response to a message.

  3. A method can modify the attributes of the object that executes the method, but cannot directly modify the attributes of any other object.

  4. One method can send messages to several different objects, and those messages can lead to modifications in their receivers.

Using the previous list of characteristics as a guide, we can use the following questions as a basis for writing preconditions and postconditions. When we are working with Jeroos, we need to consider how a method can change the attributes of the Jeroo object that executes the method. In some cases, Jeroo actions like pick(), plant(), and toss() can change the attributes of the world by adding or removing objects, although we normally don't send messages to these other object directly. Behind the scenes, the pick(), plant(), and toss() methods send appropriate messages to the island in order to add or remove objects corresponding to the desired behavior.

Precondition QuestionsPostcondition Questions
Do any of the attributes of the receiving object need to have special values?
Location
Direction
Flowers
How does this method affect the attributes of the receiving object?
Location
Direction
Flowers
Are the contents of certain island cells important? Have the contents of any island cells changed?

The preconditions and postconditions can be created rather informally, but the final versions should be stated in a comment block at the beginning of the source code for the method. As an example, consider the method from the previous section to plant four flowers in a row:

// ----------------------------------------------------------
/**
 * Plant four flowers in a row, starting at the current location.
 *
 * @precondition The three spaces directly ahead of the Jeroo are clear.
 * @precondition The Jeroo has at least four flowers.

 * @postcondition The Jeroo has planted four flowers, starting at its
 *                current location and proceeding straight ahead.
 * @postcondition The Jeroo is standing on the last flower, and facing in
 *                its original direction.
 */
public void plantFour()
{
   this.plant();   // -- one ---

   this.hop();
   this.plant();   // -- two ---

   this.hop();
   this.plant();   // -- three ---


   this.hop();
   this.plant();   // -- four ---
}

The section contains an extended example that demonstrates the algorithm development process, and shows a recommended process for developing source code that contains Jeroo methods.

Problem Statement (Step 1)

A Jeroo starts at (4, 1) facing North with 5 flowers in its pouch. There are two nets immediately South of the Jeroo at locations (4, 2) and (4, 3). There is a flower directly South of the second net. Write a program that directs the Jeroo to disable the nets and pick the flower. After picking the flower, the Jeroo should return to its starting location and face South.

StartFinish
The starting situation for example 5.1 The finishing situation for example 5.1

Analysis of the Problem (Step 2)

  1. The Jeroo must turn around to locate the first net

  2. Each net is directly South of the previous one

  3. The first net is directly South of the Jeroo

  4. The flower is at location (4, 4)

  5. The Jeroo must finish facing South at location (4, 1)

  6. The Jeroo should finish with 5 - 2 + 1 = 4 flowers

Detailed Algorithm (Steps 3 and 4)

Let's name the Jeroo Kim. Kim should do the following:

Turn around // now at (4, 1) facing South
Disable two nets in a row
Toss
Hop once // now at (4, 2) facing South
Toss
Hop once // now at (4, 3) facing South
Get the flower
Hop once // now on flower at (4, 4) facing South
Pick
Go back to (4, 1) and turn around
Turn around // now at (4, 4) facing North
Hop 3 times // now at (4, 1) facing North
Turn around // now at (4, 1) facing South

Review the Algorithm (Step 5)

  1. The high-level algorithm helps manage the details.

  2. We used a "turn around" step in example 4.2. We can use the same logic here.

  3. The act of turning around appears as a step in the high-level algorithm and as part of the "Go back to (4, 1) and turn around" step. Interesting!

Possible Behaviors

  1. "Turn around" is used three times

  2. The sequence "Toss, Hop" is used two times in the "Disable nets" step.

We will create a custom Jeroo subclass and write a Jeroo method for each of these behaviors, but first, we need to define a purpose, preconditions, and postconditions for each method. This can be done informally, because we will write these things in a comment block at the beginning of each method.

Method: turnAround()
Purpose: Make the Jeroo turn 180 degrees
Preconditions:
none
Postconditions:
The Jeroo has turned 180 degrees
The Jeroo is at the same location
Method: tossAndHop()
Purpose: Disable a net and move to the newly cleared location
Preconditions:
There is a net ahead
The Jeroo has at least one flower
Postconditions:
The net has been disabled
The Jeroo has one less flower
The Jeroo is at the location originally occupied by the net
The Jeroo has not changed direction

The last postcondition of the tossAndHop() method simply says that the Jeroo is facing the direction it was facing at the start of the method. It does not prohibit the Jeroo from changing direction during the course of the method as long as the Jeroo returns to its original direction at the end.

Java Code for "Clear Nets and Pick"

As before, we should develop the code as a series of builds. To start this process, create a new scenario using Greenfoot4Sofia and select the Jeroo palette from the Edit Palettes menu. Then right-click on the island class and create a new subclass called ClearNetsAndPick for this example. Also, create a new subclass of Jeroo called ClearingJeroo to hold your Jeroo methods.

Once you have these classes created, make sure they are compiled and then right-click on your ClearNetsAndPick class and create an instance of it, which will then fill the world view. Then right-click on the flower and net classes to create new objects and drop them at the appropriate starting locations on the island. Finally, right-click on an unoccupied cell on the island and choose "Save the world" from the popup menu in order to save the starting configuration as part of your ClearNetsAndPick class.

FIRST BUILD

The recommended first build contains three things:

  1. The myProgram() method in your island subclass that creates and sends messages to the Jeroo.

  2. Declaration and instantiation of every Jeroo that will be used. This implies adding an appropriate constructor to our Jeroo subclass.

  3. The high-level algorithm in the form of comments.

  4. Skeletons for each of the Jeroo methods in your Jeroo subclass. These skeletons are often called stubs.

A method stub, or just a stub, is a bare skeleton of a method that will compile, but is really just a placeholder for the real method definition that will come later.

The myProgram() method goes inside your ClearNetsAndPick class:

public void myProgram()
{
    ClearingJeroo kim = new ClearingJeroo(4, 1, NORTH, 5);
    this.add(kim);

    // --- Turn around ---

    // --- Disable nets ---

    // --- Get the flower ---

    // --- Go back to (4, 1) and turn around ---

}

An appropriate constructor and the new Jeroo methods go inside your ClearingJeroo class:

// ----------------------------------------------------------
/**
 * Create a new Jeroo.
 * @param x         The x-coordinate of the Jeroo's location.
 * @param y         The y-coordinate of the Jeroo's location.
 * @param direction The direction the Jeroo is facing.
 * @param flowers   The number of flowers the Jeroo is holding.
 */
public ClearingJeroo(int x, int y, CompassDirection direction, int flowers)
{
    super(x, y, direction, flowers);    // Let the superclass initialize these
}

// ----------------------------------------------------------
/**
 * Turn the jeroo around 180 degrees so it faces the opposite
 * direction.
 *
 * @precondition  None.
 *
 * @postcondition The Jeroo has turned 180 degrees.
 * @postcondition The Jeroo is at the same location.
 */
public void turnAround()
{
}


// ----------------------------------------------------------
/**
 * Disable a net and move to the newly cleared location.
 *
 * @precondition  There is a net ahead.
 * @precondition  The Jeroo has at least one flower.
 *
 * @postcondition The net has been disabled.
 * @postcondition The Jeroo has one less flower.
 * @postcondition The Jeroo is at the location originally occupied by the net.
 * @postcondition The Jeroo has not changed direction.
 */
public void tossAndHop()
{
}

SECOND BUILD

This build finishes the turnAround() method and uses it in the myProgram() method. It would be wise to test this method four times, each time start with Kim facing in a different direction. Once we are comfortable that this method works correctly, we can proceed with the next build.

In the ClearNetsAndPick class:

public void myProgram()
{
    ClearingJeroo kim = new ClearingJeroo(4, 1, NORTH, 5);
    this.add(kim);

    // --- Turn around ---
    kim.turnAround();                 // new code

    // --- Disable nets ---

    // --- Get the flower ---

    // --- Go back to (4, 1) and turn around ---

}

In the ClearingJeroo class:

// ----------------------------------------------------------
/**
 * Turn the jeroo around 180 degrees so it faces the opposite
 * direction.
 *
 * @precondition  None.
 *
 * @postcondition The Jeroo has turned 180 degrees.
 * @postcondition The Jeroo is at the same location.
 */
public void turnAround()
{
    this.turn(LEFT);                  // new code
    this.turn(LEFT);                  // new code
}


// ----------------------------------------------------------
/**
 * Disable a net and move to the newly cleared location.
 *
 * @precondition  There is a net ahead.
 * @precondition  The Jeroo has at least one flower.
 *
 * @postcondition The net has been disabled.
 * @postcondition The Jeroo has one less flower.
 * @postcondition The Jeroo is at the location originally occupied by the net.
 * @postcondition The Jeroo has not changed direction.
 */
public void tossAndHop()
{
}

THIRD BUILD

This build finishes the tossAndHop() method and uses it in the myProgram() method. Our focus is on destroying the two nets.

In the ClearNetsAndPick class:

public void myProgram()
{
    ClearingJeroo kim = new ClearingJeroo(4, 1, NORTH, 5);
    this.add(kim);

    // --- Turn around ---
    kim.turnAround();

    // --- Disable nets ---
    kim.tossAndHop();                 // new code
    kim.tossAndHop();                 // new code

    // --- Get the flower ---

    // --- Go back to (4, 1) and turn around ---

}

In the ClearingJeroo class:

// ----------------------------------------------------------
/**
 * Turn the jeroo around 180 degrees so it faces the opposite
 * direction.
 *
 * @precondition  None.
 *
 * @postcondition The Jeroo has turned 180 degrees.
 * @postcondition The Jeroo is at the same location.
 */
public void turnAround()
{
    this.turn(LEFT);
    this.turn(LEFT);
}


// ----------------------------------------------------------
/**
 * Disable a net and move to the newly cleared location.
 *
 * @precondition  There is a net ahead.
 * @precondition  The Jeroo has at least one flower.
 *
 * @postcondition The net has been disabled.
 * @postcondition The Jeroo has one less flower.
 * @postcondition The Jeroo is at the location originally occupied by the net.
 * @postcondition The Jeroo has not changed direction.
 */
public void tossAndHop()
{
    this.toss();                      // new code
    this.hop();                       // new code
}

FOURTH BUILD (final)

This build finishes the myProgram() method. We need to check to see that Kim has the correct number of flowers at the end.

In the ClearNetsAndPick class:

public void myProgram()
{
    ClearingJeroo kim = new ClearingJeroo(4, 1, NORTH, 5);
    this.add(kim);

    // --- Turn around ---
    kim.turnAround();

    // --- Disable nets ---
    kim.tossAndHop();
    kim.tossAndHop();

    // --- Get the flower ---
    kim.hop();                        // new code
    kim.pick();                       // new code

    // --- Go back to (4, 1) and turn around ---
    kim.turnAround();                 // new code
    kim.hop(3);                       // new code
    kim.turnAround();                 // new code
}