Using inheritance to factor out common code

Chapter: Gardening
...Section: Using inheritance to factor out common code

Now you're going to do this whole lab over again! But better. A practical step now is to copy the java classes and the images in your lab1 directory into a new directory lab1Better. To keep things easy for the grader, lab1Better should be a subdirectory of lab1. If your images are jpgs with extension jpg or jpeg, then you can achieve this with the two unix commands

mkdir lab1Better
cp *.j* lab1Better
All the changes from now on will be to the copies of classes that you have in lab1Better. Begin by going into that directory: cd lab1Better.

Note: If you're using BlueJ or Eclipse you'll need to achieve similar results by creating a new project and placing it appropriately.

You will have noticed that the Flower and Weed classes share most of their code. It is considered to be bad style to have lots of shared code between classes. Instead it is preferable to move the shared code into a superclass. Design a new abstract class, Plant, which will implement PlantInterface. Its header should look like

public abstract class Plant implements PlantInterface { 

Copy all of the instance variables and methods from Flower to Plant except for the toString method and the constructor. The constructor for Plant should take seven parameters: the width, height, x and y coordinates, an Image, the growth rate, and a Component to draw on, each of which should be saved in an instance variable. All of your instance variables should be declared to be protected and not private, so they will be accessible in subclasses.

While the abstract class Plant has a constructor, you may not invoke it because the class is declared to be abstract. Instead the constructor is defined solely so that it can be extended by other classes. A fast way to create Plant is to edit the existing Flower.java file that you copied into this directory.

Now go again to your copy of the file Flower and change the header so that it extends Plant. You no longer need to say that it implements PlantInterface, as Java can deduce that from the fact that it extends a class that implements the interface. Now erase all of the methods except for toString. The other methods will be inherited from Plant. Also erase all of the instance variables. [Very bad things happen if you repeat the declaration of instance variables from a superclass into a subclass - the compiler treats them as distinct from those declared in the superclass.] However, keep any constant declarations.

The constructor of Flower can now be greatly simplified, as you need only invoke the constructor of the superclass, using the appropriate parameters. The constructor of Flower takes four parameters, the x and y coordinates of the flower, the Image, and the canvas. The height and width are not passed in as all flowers have the same initial width, height, and growth rate (which hopefully you declared as constants when you originally wrote the class). You can now replace almost the entire constructor body by a call of the superclass constructor, written as:

super(...,...,x,y,flowerImage,...,canvas); 
where the "..." are replaced by the constants representing the inital width and height and growth rate of the flower. The call to super must be the first statement of the Flower constructor. Notice that the toString method refers to instance variables declared in Plant. This is possible because they are declared to be protected rather than private. Protected variables are accessible from within subclasses, while private variables are not. Again, make sure you have not repeated the declarations in the subclass.

You will make no changes at all the the file StartIt.java nor to the file StartThem.java nor to the file FlowerGarden.java.

Now, if you have done all of this correctly, you should be able to compile all of the classes and execute the StartIt and StartThem classes, and get the exact same results as you did before. If you have errors, track them down - especially making sure that you got rid of all of the instance variables from Flower.

This was a lot of extra work, but if you had done this originally, it would have been much easier to write the Weed class and any other classes with similar behavior. Now let's change the Weed class so that it also inherits from Plant. Start by changing the header to declare it extends Plant. Again, all of the necessary instance variables were declared in Plant, so should not be repeated in Weed. The constructor will be modified so that it is similar to that in the revised Flower class.

The Weed class will need two methods, toString and grow. The toString method will remain as it was (indicating that it is a "weed"), while the grow method is different from that of Plant, in that now the width must also increase as it grows. Thus the grow method needs to perform the same actions as the method in Plant, but then also do more. If we add a grow method to the Weed class, then it will replace the grow method inherited from Plant. However, we would like to perform the actions of the grow method from the superclass, as well as the extra operations. We can execute the code in the superclass, by including the line: super.grow(); inside the method body. Then just add the extra code to update width after that line.

Without making any changes to WeedGarden.java nor to StartWeeds.java, you should find that the class StartWeeds executes exactly as before.


rhyspj@gwu.edu