Object-Oriented Design of Frogger |
As we have seen above, when we design a class, we create a model of some object in the real world. The key points were that:
We also mentioned that you need to distinguish those parts of the real world that you want to model from those that you do not.
Now, recall frogger.
First, what objects appear in the frogger game?
Let's focus on the frog initially. What are the properties of a frog?
How do we represent these? With instance variables.
What are the behaviors of a frog?
How do we represent these behaviors? With methods.
Let's also think about whether we need getters and setters for our instance variables. Should we allow other classes to change the appearance of the frog? No. Should they be able to access the appearance? Probably not necessary. What about location? Again, we probably don't want any other classes to set the location arbitrarily. Instead our hop and reincarnate methods will move the frog in more controlled ways. What about setting its alive/dead status? Well, when a car hits the frog, we probably want the car to tell the frog it is dead. The frog becomes alive again when it is reincarnated. Is it useful for other classes to know if a frog is alive or not? Maybe cars should interact differently with alive and dead frogs. So, we probably do want an accessor method for the aliveness property.
Finally, we need to think about how we will construct a frog. We must give each instance variable a value. In some cases, we need information outside of the frog to determine reasonable values for those instance variables. How do we communicate that information to the frog? By passing parameters to the frog constructor. How will we initialize each instance variable?
We have now figured out a lot of pieces of the Frog class. Let's see what we have so far:
public class Frog { private VisibleImage frogImage; private boolean alive; public Frog (Image frogPic, Coords startLoc){} public void hop (??what goes here??) {} public void sayOuch (??what goes here??) {} public void reincarnate (??what goes here??) { alive = true; anything else? } public void kill (??what goes here??) { alive = false; ??anything else?? } public boolean isAlive (??what goes here??) { return alive; } }
Questions about the design so far:
public void kill (??what goes here??) { alive = false; add code to say ouch ??anything else??? }
Are we done with the design of Frog? Probably not. As we think about what we need to do to implement these methods we are likely to uncover more details. But we have designed a good abstraction. Now is a good time to move on to another part of the problem.
We can go through the same exercise with the Vehicle class thinking about its properties and its behaviors. What are its properties:
What are its behaviors:
What kind of thing is a vehicle? It is self-animated; it moves without requiring user input. How do we implement such things? By extending ActiveObject. When we extend ActiveObject, what do we do?
while (not at end of highway) { move a little pause a little } disappear
This design provides all the behavior we want except for killing the frog. How will we do that? Whenever we move the car, we should check to see if it hit the frog? How does the Vehicle know where the frog is? It doesn't. We need to communicate this information to the Vehicle. When should we tell it about the frog? The only time we interact with an active object is when we create it, so we have to tell it about the frog then. How does that information get to the run method? We save it in an instance variable. In this case the frog instance variable is not really a property of the vehicle but it is information that the vehicle needs to know in order to give us the behavior we want. This is an example of how we need to not just understand the object in isolation but its context in the larger program to design it correctly.
Now, where do vehicles come from? They are a lot like the falling leaves that we saw. There are many of them on the screen at once. How did we create falling leaves? With a tree class. The tree class was itself an active object that continuously generated leaves. We want the same thing here - something that will continuously generate vehicles. What is that class? A highway?
Watch the cars driving across the screen. What do you observe? They drive in lanes. Some lanes go one direction; some go the other. Different types of vehicles drive in each lane. All the vehicles in a lane go the same speed, but the vehicles in different lanes go at different speeds. In short, there are similarities among the vehicles in a lane and differences when we look at different lanes. How can we capture that? By creating a Lane class.
What are the properties of Lane?
What is its behavior?
Let's think carefully about what the parameters to the Lane constructor should be. Recall that we need to initialize all the instance variables. As a result, we need parameters describing:
What kind of class is Lane? It's another ActiveObject. What does its run method do? It creates Vehicles:
while (??what???) { create a vehicle pause a little } what do we do when we are done?
How do we create a vehicle? We call its constructor. What information does the Vehicle constructor need? Appearance, location, speed, direction, and the frog. The Lane already received all of this in its constructor except for the frog. Where does it get information about the frog? It needs to be communicated to the Lane. How? In the constructor. So, we need to have a Frog parameter in our Lane constructor. We save the frog in an instance variable so that it can then be passed to the Vehicle constructor in the Lane's run method.
We need one more class to make our program complete. We need to handle the user input. What user input do we care about? Just mouse clicks. We call this class Frogger. Its responsibility is to get the program started and then call the appropriate methods when the user clicks the mouse. What kind of object is it? It is our familiar friend WindowController.
Object-Oriented Design of Frogger |