Strategies and Powertypes

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.

PAnimals 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:

The Classes

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.

Sequence Diagrams

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:

  1. 1Tell everyone to erase

  2. 2Tell everyone to move

  3. 3Each animal then resolves its interactions

  4. 4Tell everyone to redraw themselves

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 PAnimals. Once this is accomplished, it must feed this information back to PSimulator, so that other affected PAnimals 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.

Creating an Animal or Species

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.

Singleton Pattern

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;
}

Prototyping

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.

© 1998 by Wrox Press. All rights reserved.