The requirements analysis dictates that Phish should be able to generate new "species" while the game runs (through mutation, etc.). This is particularly tricky in a language such as C++ as it is not possible to create new classes "on the fly".
We will create a powertype (discussed in Chapter 3) PSpecies
which will act as the discriminator for the PAnimal
class. Note that each concrete Phish class will begin with the prefix P.
Each instance of PAnimal
will have a reference to a single instance of PSpecies
, but many animal instances can refer to the same instance of PSpecies
. That instance of PSpecies
will describe a single species of fish. Thus, if we have Shark
, Tuna
and Minnow
, there will be three instances of PSpecies
. The first instance will have its attributes set appropriate for shark, the second for tuna and the third for minnow.
Each Shark
object (an instance of PAnimal
) will have a pointer to the shark species instance. Each Shark
object will know its own location, but the rules for its behavior will be dictated by the PSpecies
object to which it points.
PAnimal
s will not be allowed to change their species, so the pointer must be constant throughout the game. The PSpecies
instances will in turn delegate responsibility for determining their mating, feeding and movement behavior to a series of strategy objects, as shown in the diagram below:
Let's review the current class diagram. PADisplay
is a development of the Display
class identified earlier, Phish
is now PPhishTank
, and the environment is now managed by a PSimulator
class. The big change is in the representation of the different sorts of fish:
The PAnimal
and PSpecies
classes are aggregated by the PSimulator
class, which is responsible for generating the clock tick that we discussed earlier. The PSimulator
class is aggregated in turn by the PPhishTank
which also aggregates the PADisplay
.
The goal is to separate out responsibilities and to position the design for portability across platforms. Responsibility for displaying the Phish is delegated to the PADisplay
class. The prefix PA
indicates that this is an abstract type. The concrete classes derived from PADisplay
will manage the platform-specific display details.
The PPhishTank
thus becomes an agent, linking the display to the simulator. The simulator is responsible for the logic of the game, the display is responsible for the user interface, and together they compose a Phish tank which represents the totality of the game.
This design may be mapped to the MFC document/view design in various ways, depending on how we want to balance ease-of-programming with portability. If we make the PPhishTank
class derive from CApplication
, the PADisplay
class derive from, for example, CDialog
and the PSimulator
class derive from CDocument
, then we fit cleanly into the MFC architecture. Alternatively, we can derive a concrete display class (CDisplay
) class from PADisplay
and have CDisplay
aggregate and delegate to the MFC view class (or a Windows window directly). Similarly, we can set aside the CDocument
class, or we can have the PPhishTank
object wrap the document class. The question is about the level of isolation required to allow for future portability.
The next step in fleshing out the design is to consider how the various objects might interact. To begin with, we'll sketch out a number of interactions. The core interaction involves the PAnimal
instances receiving a Tick
message from PPhishTank
.
Each tick of the game clock represents a time slice in the game. Within each time slice, each animal may move and if it encounters another fish it may either eat (or be eaten by) that fish, if it is of a different species, or mate if it are of the same species.
This is demonstrated by the following sequence diagram:
The PPhishTank
sends the Tick
message to the simulator. The PSimulator
object understands a simple algorithm:
Note that this algorithm can easily be migrated down into the PAnimal
class if PAnimal
becomes a thread in its own right. For now, the PAnimal
is told to erase itself. It collaborates with the display to erase itself. It is then told to move. It gets the MovementStrategy
from the PSpecies
so that it knows how far to move. It then asks PSpecies
for the various interaction strategies (eating and mating) so that it can resolve its interactions with other PAnimal
s. Once this is accomplished, it must feed this information back to PSimulator
, so that other affected PAnimal
s can be updated (e.g. any PAnimal
object that this one eats should be removed from the simulation). Finally, the simulator tells the PAnimal
to draw itself. The PAnimal
delegates this to its PSpecies
(passing in its location), because it is the PSpecies
that knows what color and icon to use.
When two fish mate, a new fish is created. The work of creating the Animal is delegated by the simulator to the Species. It is the species which knows the range of possible values for each characteristic of the new animal. The specific value within that range is 'assigned by God' — that is to say, here it is stochastic.
PPhishTank::CreateAnimal (string Name, Location location)
The CreateAnimal()
method takes two parameters: the Name
of the species and a location
object. The location
object represents the site of the mating of the parent fish. There is no privacy in this game!
Animal creation is delegated to the PSimulator
, which obtains the PSpecies
object for the species it needs. This PSpecies
is a singleton, which knows in turn how to create the right kind of PAnimal
. The PSpecies
creates the PAnimal
, and inserts it in the PSimulator
with which the PSpecies
is associated. The Factory Pattern will be used to allow creation of a derived animal by a derived species.
It is not uncommon that your design calls for a class which will never have more than a single instance. For example, in a given system there may be only a single database. An application may want to ensure that only one instance of itself is instantiated at any one time; or that there is only one open document on which there will be various views.
The Singleton Pattern (see the Design Patterns book by the Gang of Four) is typically implemented by providing a public static method (often named Instance()
), which returns a pointer to a static instance of itself, thus:
Class myClassWithASingleton
{
public:
// all creation is through this method
static myClassWithASingleton* Instance()
protected:
myClassWithASingleton(); // constructor
private:
static myClassWithASingleton* theInstance
};
The Instance()
method checks to see if theInstance
is null, and if so, it creates a new instance and returns the pointer, otherwise it just returns the pointer.
// allocate the pointer and initialize to null
myClassWithASingleton* myClassWithASingleton::theInstance = 0;
myClassWithASingleton* myClassWithASingleton::Instance()
{
if ( ! theInstance )
theInstance = new myClassWithASingleton;
return theInstance;
}
These preliminary design decisions are enough to begin prototyping the application. We decide to implement Phish in five iterations, each of which should take approximately one week of development time. We will work incrementally, so that each iteration builds on the work of the previous one:
The goal of the prototype is to create working code early and then to improve and expand on this code as we iterate over the design and the implementation.
The entire first release will take approximately 5 weeks of development.