Comp 170 Lab 9 - moving things to the parent class

When we looked at version 2 of the Fox&Rabbit simulator, we initially moved to class Animal only those things that could make the transition unchanged. That included the fields age, alive, and location (for which we had to create accessors and mutators so they would continue to be accessible in the derived classes Fox and Rabbit), and the basic methods isAlive and setLocation. That doesn't go too far at reducing duplicated code or clearing up the remaining similarities.

In version 3 we then moved the incrementAge() method to class Animal. The code (below) is identical for Fox and Rabbit, but the field MAX_AGE is different in those two classes, and for class fields (as opposed to class methods) there is no "dynamic search" for the most-specific version. So moving the code as-is to Animal would mean that the variable MAX_AGE as defined in class Animal would be used, which we don't want. However, by replacing MAX_AGE with a call to a method maxAge(), and supplying a maxAge() method specific to each of the classes Fox and Rabbit (and also an abstract version in class Animal), we can have a single version of incrementAge in class Animal that behaves differently (due to different values returned by maxAge()) for Rabbits and Foxes.

    private void incrementAge()
{
age++;
if(age > MAX_AGE) { // new: if (age > maxAge())
alive = false;
}
}

Your assignment is to do the same thing for the following methods (currently in Fox & Rabbit): uniformize the Fox and Rabbit versions, and move the result to Animal. Start with the version of Fox & Rabbits here.

The first two are straightforward and in the style of incrementAge(): just replace the constants in the method bodies with accessor methods, and move the result to class Animal. Don't forget to delete (or comment out) the methods from Rabbit and Fox. You'll also have to verify that all the neccessary accessor methods are in place (I added most of them), and you may have to adjust some public/private settings.

To move act() to Animal, the finding-a-new-location portion has to be uniformized between Foxes and Rabbits; the remaining difference between Fox.act() and Rabbit.act() is in this code where newLocation is set.

This time the difference cannot just be eliminated; the way Foxes and Rabbits each find their new location is fundamentally different. So what we will do to get a single version of act() is to move the location-determining part of the act() code to a new method, newLocation(). I've already created these methods for you; they're at the bottom of Fox.java and Rabbit.java respectively. Replace the relevant code in act() with calls to these methods and then the two versions of act() are identical and thus this new common version (but not newLocation()) can be moved to Animal. Note that Fox.newLocation() and Rabbit.newLocation() are not identical and cannot be "uniformized" into Animal; this is a place where the two creatures behave fundamentally differently. Do not move the if (newLocation != null) stuff out of act().

You will have to create an abstract incrementHunger() and abstract newLocation(), and possibly other methods, in Animal. Commented-out versions of some of these appear at the bottom of Animal.java. In general, try to keep methods abstract when that works (ie when the Rabbit and Fox classes should have to override the method). Some existing methods may have to change from private to public, as well.

Now let's look at addBabies. In the two existing versions, one has variable newFox and the other newRabbit. Although these are different types, it turns out that you can just use the name newAnimal, of type Animal, for almost everything.

This leaves just one last non-uniformized part: Rabbit has new Rabbit(false) and Fox has new Fox(false). At first glance, these might seem difficult to uniformize: we simply have to specify which animal we're creating! Here's one (deprecated!) workaround, that checks the type of the current object and creates an appropriate baby based on that:

    Animal babyAnimal;
if (this instanceof Rabbit) {
babyAnimal = new Rabbit(false);
} else {
babyAnimal = new Fox(false);
}
While this works, use of instanceof is often best avoided. The alternative (and better) approach, using polymorphism instead, is to create another new method, this time Animal newAnimal(). The Fox version will return a new Fox, the Rabbit version will return a new Rabbit, and polymorphism will make sure the right version gets invoked. Stubs for newAnimal() are in place, and Rabbit.newAnimal() is implemented.

Once you're done with all this, pretty much all that's left in the Fox and Rabbit classes is code that has to be there: code that somehow represents fundamental differences between how these two classes behave. Sometimes the fundamental difference is just that a different constant value is returned (as in maxAge()); sometimes the algorithm is different (as in newLocation()). Either way, the behavioral differences between Fox and Rabbit are now spelled out, as are the similarities.

Email me your completed project.