Aquarium Lab Series    

Lab: Fish With Class

Implementing Classes

Alyce Brady
Kalamazoo College


This set of Lab Exercises is the fifth in a series in which students build a small program with several fish moving around in an aquarium. The set includes the following exercises:

Each section contains an Introduction to a problem or task and an Exercise.

In the exercises that precede this one, students will have created a vector of fish that move randomly back and forth in an aquarium, being careful not to hit the sides. Students should understand how to identify the responsibilities of different classes, construct class methods, and use instance variables within methods.



Ascending and Descending Fish

Introduction

Our program would be much more interesting if the fish moved up and down as well as side to side. In this exercise, you will implement two new methods in the AquaFish class, ascend and descend, to support this behavior.

Exercise: Simulation Up and Down Movement

  • To make the simulation more believable, the distance that a fish moves up or down should be related to its height. Fish come in different sizes, so the size of any particular fish is one of the properties of that fish. Its position in the aquarium is another relevant property for this exercise. Read the implementation (code) for the AquaFish class to determine which methods or instance variables will be useful in implementing ascend and descend.

  • Determine what parameters (if any) you will need for the new ascend method. Then determine what its return type should be. Add an empty ascend method (one that consists of a declaration and empty braces) to the AquaFish class after the moveForward and changeDir methods.

  • Implement the ascend method. You may use the moveForward method as a guide if you like, but the ascend method is simpler. The movement amount should simply be the height of the fish. Research the specification for the AquaPoint class to discover what methods might be useful in implementing the ascend and descend methods.

  • Implement the descend method.

  • Modify your main method in the AquaSimApplication class to allow fish to ascend or descend before moving forward, according to the following formula:
    • A fish at the surface has a 2/3 chance of descending and a 1/3 chance of staying at the surface.
    • A fish at the bottom has a 1/3 chance of ascending and a 2/3 chance of staying at the bottom.
    • A fish that is neither at the surface nor at the bottom has a 1/3 chance of ascending, a 1/3 chance of descending, and a 1/3 chance of staying at the same depth.
    Read the implementation (code) for the AquaFish class to determine what methods are available to tell whether a fish is at the surface, at the bottom, or somewhere in between.  (These methods do not appear in the partial specification for the AquaFish class that you have used previously.)

  • Test your program.



Responsible Fish

Introduction

One of the most important tasks in designing and implementing object-oriented programs is deciding which classes or objects are responsible for executing which behavior. Up until now, all of the behavior that you have added to the Aquarium Simulation program has been in the main method of the AquaSimApplication class. Now it's time to make the program more object-oriented. For example, deciding whether a fish should change direction or ascend or descend when it moves should be the responsibility of the fish, not of the simulation program.

In this exercise, you will implement a move method in the AquaFish class that will encapsulate all of the behavior related to movement (ascending, descending, changing direction, and moving forward).

Exercise: Make Fish Responsible for Knowing How to Move

  • Analyze the main method of the AquaSimApplication class to determine which lines of code should be moved to the move method you will be implementing in the AquaFish class.

  • Determine what parameters (if any) you will need for the new move method. Then determine what its return type should be. Add an empty move method (one that consists of a declaration and empty braces) to the AquaFish class before the moveForward method.

  • Move the appropriate lines of code from the main method in AquaSimApplication to the move in AquaFish. Notice that the main method was invoking AquaFish methods such as changeDir on a named fish. The move method, though, is tied to the AquaFish class. More specifically, it is always tied to (or executed by) a particular fish. Thus, instead of main invoking methods such as changeDir on a named fish, the fish should now be invoking methods on itself.

  • Modify the main method in AquaSimApplication to simply tell each fish to move, letting the fish worry about how it should move.

  • Test your program to verify that the behavior is unchanged.



Modeling a Simulation

Introduction

In a well-designed object-oriented program, we usually want the main function to just create some objects and get the ball rolling. Most of the program behavior should be the result of the objects interacting with each other. In the Aquarium Simulation program, though, the main function is actually running the simulation. We have objects that model the fish and the aquarium, but not one that models the simulation itself.

In this exercise, you will write the code for methods in a Simulation class. The constructor will initialize the Simulation object's instance variables and construct the fish in the aquarium. The step method will execute the commands that should happen each timestep in the simulation (moving the fish, in our case).

Once you have implemented the Simulation class, the main method in AquaSimApplication will merely create a number of objects, such as the aquarium, the graphical user interface, and the simulation object, and then ask the simulation object to run the simulation. The main method will also still display the initial configuration of the fish and the modified aquarium after each timestep.

Exercise: Introduce the Simulation Class

  • Read the specification for the Simulation class to familiarize yourself with what the class should do.

  • Download the incomplete Simulation class implementation if you don't have it already.

  • Read the incomplete Simulation class implementation and then modify the constructor to initialize the instance variables.

    Notice that Simulation objects have three instance variables and that the constructor takes three parameters. Two of the three instance variables represent the same objects as two of the parameters, so you can initialize them directly. For example, the main method in AquaSimApplication will construct an Aquarium and pass it to the Simulation constructor. The Simulation object should refer to the same Aquarium object, not a new one. On the other hand, looking at the parameters to the Simulation constructor, we can see that the main method will not construct an array of fish and pass it to the constructor. Instead, it will pass the number of fish that should be in the array. Therefore, to initialize the allFish instance variable, you will need to construct a new array of fish.

  • Move the appropriate lines of code that construct various colored fish (but not the lines that display them) from the main method in AquaSimApplication to the Simulation constructor. Double-check whether the instance variables in the Simulation constructor have the same names that the local variables in the main method had.


  • Stop and Think

    Now that the fish are not constructred in the main method, how can the code pass them to the user interface to display? Research the class documentation for the Simulation class to find a method that will be useful in solving this problem.


  • Replace the code in the main method that constructed all the fish with a statement that constructs a Simulation object.

  • Determine what code in main represents a single timestep in the simulation. Move that code to the step method in Simulation. Adjust any variable names if necessary.

  • Replace the code in the main method that represented a single timestep in the simulation with a call to the step method in Simulation.

  • Test your program to verify that the behavior is unchanged.

Analysis Questions

  • Notice that the constructor uses instance variables, parameters, and local variables. For example, the following statement
    
      allFish.add(new AquaFish(aqua, color));
    
    uses two instance variables and two local variables. How can you determine whether a given identifier is a local variable, a parameter, or an instance variable?

  • If you had the following code
    
      ArrayList<AquaFish> allFish = new ArrayList<AquaFish>();
    
      // WARNING!
      ...
      allFish.add(new AquaFish(aqua, color));
    
    in the constructor, would you be putting new fish in the instance variable called allFish or in a local variable? How would this affect the behavior of the step method?



Adding Step and Run Buttons

Introduction

The user of the Aquarium Simulation program can control how many fish to put in the aquarium and how many simulation steps to run, but cannot control the pace of the simulation. We can give the user more control over how the simulation runs by providing Step and Run buttons.

Exercise: Allow User to Control Simulation

  • The AquaSimGUIwithSimulation class is a subclass of AquaSimGUI. It has step and run buttons added to the user interface that control the running (and displaying) of the simulation. It also prompts the user for the number of fish and simulation steps for you, and constructs a Simulation object. Research the abbreviated specification for the AquaSimGUIwithSimulation class to discover how to modify your code to construct an object of type AquaSimGUIwithSimulation instead of type AquaSimGUI.

  • Modify the main method in AquaSimApplication to construct the graphical user interface. Once you have done this you no longer need to ask for the number of fish or number of simulation steps, construct the Simulation object, display the aquarium, or run the simulation. The graphical user interface will do all of that. Therefore, the construction of the graphical user interface should be the last line of code before the WRAP UP phase.

  • Test your program to verify that the behavior is unchanged.

  • Make sure that you have updated the program documentation in all modified classes to reflect your changes.




Copyright Alyce Faulstich Brady, 2001-2002.