Gary Jaye's Guide to the Marine Biology Case Study (3)

Back to Contents     To previous section    MCX1 home     MCX2 home

6. The Environment::Update function and Brady's object diagram: (under  construction)

On the basis of what you have learned about the Marine Biology case study in the previous five sections, you should be able to examine Alyce Brady's object diagram and the member function prototypes that accompany it to formulate a credible outline of how to accomplish the task specified in Environment::Update's prototype. On the basis of the (valid) outline you will be asked to write, a valid implementation of Environment::Update should follow routinely.


void Environment::Update(const Position & oldLoc, Fish & fish);
// precondition: fish was located at oldLoc, has been updated
// postcondition: if (fish.Location() != oldLoc) then oldLoc is empty;
// Fish fish is updated properly in this environment

bool Environment::InRange(const Position & pos) const;
// postcondition: returns true if pos in grid,
// returns false otherwise

int Fish::Id() const;
// precondition: ! IsUndefined()
// postcondition: returns id number of fish

Position Fish::Location() const;
// postcondition: returns current fish position

int Position::Row() const;
// postcondition: returns row of Position

int Position::Col() const;
// postcondition: returns column of Position


Question 6.1:
Use Brady's object diagram and the prototypes to formulate an outline that specifies how  Environment::Update can achieve its task.

Question 6.2: Complete Environment::Update started below.

void Environment::Update(const Position & oldLoc, Fish & fish) {
// precondition: fish was located at oldLoc, has been updated
// postcondition: if (fish.Location() != oldLoc) then oldLoc is empty;
// Fish fish is updated properly in this environment

}


7. Projects:
(under  construction)

1. Exam A candidates: Implement Environment::myWorld as an apvector of Fish objects.
2. Exam AB candidates: Implement Environment::myWorld as:
     a. a linked list of Fish objects or...
     b. a heap of Fish objects 

8. Answers to questions:

Answer 1.1: (a) In the Step and Display functions, the Environment object should be received as a reference parameter. (b) Whereas the Step function needs to modify the object signified by its parameter, the Show function should not modify it. This would be enforced by using a const qualifier to define the Show function’s  parameter.

Answer 1.2:
(a) The parameter should not be qualified with const because the Environment is modified by the function. The fact that the Fish class is the agent for the change is immaterial. (b) The program wouldn’t compile.

Answer 2.1: (a) Given the const qualifier,  the compiler prevents the owner of the function call from modifying any of its data members . (b) This would make sense if the grammar of the language allowed it. But this form the const qualifier can't be applied to free functions. (c) The definition doesn't include a copy constructor; Position();   declares the default constructor; Position(int r, int c);   declares the explicit-value constructor.

Answer 2.2: The body of the function requires only this statement: return myRow;

Answer 2.3: Again, the body of the function requires only this statement:
                       return lhs.Row() == rhs.Row() && lhs.Col() == rhs.Col();

Answer 2.4: (a) A Neighborhood object has two means for determining the number of Position values that have been stored to it: It can refer to private data member myCount or it can call the Size function whereas  (b) a free function can only call the Size function. (c) The Add function.

Answer 2.5: Three subtasks are required. First, the Add function must check if its maximum storage count has been reached. If not, its second task is to store the value of its Position parameter. Third, it must update myCount to reflect the new count of Position values. This is an abbreviated version of the authors' implementation found in nbrhood.cpp:

void Neighborhood::Add(const Position & pos) {
    if (myCount < myList.length())   {
         myList[myCount] = pos;
         myCount++;
    }
}

Answer 2.6: (a) The default constructor. (b) Experienced programmers try to avoid conditions like  (myCount < 4) because they make programs difficult to revise and maintain. Suppose, for example, that the capacity of Neighborhood objects was to be increased from four to eight. By using the condition (myCount < myList.length()) in the Add function, the authors have eliminated the need to revise the function. Consider the hypothetical case of revising a longer program in which many member functions referred to the capacity of a Neighborhood object: It would be all to easy to fail to revise all of the member functions in question. This might also prove to be a difficult error to detect when testing the program since a member function that had not been properly revised would do little to call attention to itself!

Answer 2.7: Constructor initializer lists were emphasized in the Large Integer case study and are being emphasized in the Marine Biology case study that replaced it. The given implementation works, but there are situations in which constructor initializer lists are a neccessity (for a  well-chosen example, see C++ for You ++, AP Edition, Maria Litvin and Gary Litvin, p. 365). This short implementation uses a constructor initializer list.

Neighborhood::Neighborhood() :  myList(4),   myCount(0) 
{
} // empty body since no program statements were needed here

Answer 3.1: IsEmpty is public, and InRange is private.

Answer 3.2: (a) AllFish returns an apvector of Fish objects; we saw it invoked in the Simulation::Step function. (b) Evidently, it ensures that a Position object represents a valid subscripted address in the apmatrix myWorld.

Answer 3.3:
Consider member functions NumRows and NumCols.
    a. They return myWorld.numrows and myWorld.numcols, respectively.
    b. A prototype for NumRows: int Environment::NumRows() const;
    c. Did your implementation of numRows use the const qualifier?:

int Environment::NumRows() const {
  return myWorld.numrows();
}

Answer 3.4: The Environment constructor!

Answer 3.5: AddFish is probably the most reasonable guess, and it corresponds to the actual implementation.

Answer 3.6: Given the program specifications, the values stored to myFishCreated and myFishCount must be equal.

Answer 3.7   This would enable an object outside the Environment class to add additional defined Fish objects to the Environment object. For example, if new program specifications mandated that fish births be incorporated in the simulation, the design question of how best to do this arises. In the implementation of fish birth, is it more appropriate for a Fish object to call AddFish, or is it more prudent to design a birth process in which a Fish object requests that the Environment object invoke AddFish for it? If you believe that a Fish object rather than an Environment object is the more appropriate agent to invoke AddFish in simulating a birth process, then AddFish is more appropriately qualified with a public access level. Although neither choice is strictly correct or incorrect, the goal in object-oriented programming of having objects manage themselves is more squarely met when the Environment::AddFish function can be invoked by  Fish objects that are simulating a birth process.

Programming hint: In AllFish, use a local Environment object with the same data member values as the owner (by brute force assignments or by  assigning the more convenient *this) and let it go out of scope before calling the exit function! It's ugly and impractical, but it gives you a way to test the implementation of your Environment destructor without executing 999 simulation steps.

Answers to 4.1 and 4.2:

Neighborhood Fish::EmptyNeighbors(const Environment & env, const Position & pos) const {
      Neighborhood nbrs;
      AddIfEmpty(env, nbrs, pos.North());
      AddIfEmpty(env, nbrs, pos.South());
      AddIfEmpty(env, nbrs, pos.East());
      AddIfEmpty(env, nbrs, pos.West());
      return nbrs;
}


void Fish::AddIfEmpty(const Environment & env, Neighborhood & nbrs, const Position & pos) const {

       if (env.IsEmpty(pos))  {
              nbrs.Add(pos);
       }

}

Answers 4.3 and 4.4:

Fish::Fish(int id, const Position & pos)   : myId(id), myPos(pos), amIDefined(true)
{ }  // Empty body: no need for statements due to initializer list



Fish::Fish()  : myId(0),  amIDefined(false)
{ }  // no reference to myPos here!!

Answer 4.5: Check the documentation for the default constructor of the Position class (in position.h). From this, we can conclude that one the Position default constructor justifies the given implementation of the Fish default constructor.

Answer 4.6: The manner in which the const qualifier is used in the access functions prevents them from modifying any data member of their respective owners. This would defeat the purpose of the Fish constructors.

Answer 4.7: The clumsiness here is partially attributable to the relation between the function's task and the data member on which it bases its action. An alternate approach would be to implement an IsDefined function, thereby harmonizing the function's return value with the value of data member amIDefined.

bool Fish::IsUndefined() const {
     return !amIDefined;
}

Answer 4.8: When myId equals 1, 'A' + (Id() - 1) equals 'A' + (1 - 1) or the code for 'A'. When myId equals 26, 'A' + (Id() - 1) equals 'A' +   (26 - 1) or the code for  'Z'. Therefore the possible return values (range) of the function is in 'A'..'Z'.

Answer 4.9: (a) The EBCDIC codes for 'I' and 'J' are 201 and 209, respectively. The strategy employed in the original version is based on the property that char('A' + 1) == 'B', char('A' + 2) == 'B',...char('A' + 25) == 'Z'. This is precisely the property that EBCDIC doesn't support. Since the EBCDIC codes for consecutive alphabetic characters are not always consecutive integers, implementing the ShowMe function for an EBCDIC platform would require an extensive re-write that appealed to the specific quirks of this collating sequence.
(b) To incorporate the ASCII collating sequence in a PC, the data is burnt into the computer's ROM.

Answer 5.1: This is a distilled version of the implementation found in fish.cpp.

void Fish::Move(Environment & env) {
  RandGen randomVals;
  Neighborhood nbrs = EmptyNeighbors(env, myPos);
  if (nbrs.Size() > 0) {
      // there were some empty neighbors, so randomly choose one
      Position oldPos = myPos;
      myPos = nbrs.Select(randomVals.RandInt(0, nbrs.Size() - 1));
      env.Update(oldPos, *this); // *this means this fish
  }
}

Answer 6.1:


Answer 6.2:
This is the implementation found in
environ.cpp.

void Environment::Update(const Position & oldLoc, Fish & fish) {
// precondition:  fish was located at oldLoc, has been updated
// postcondition: if (fish.Location() != oldLoc) then oldLoc is empty;
//                Fish fish is updated properly in this environment
  Fish emptyFish;
  if (InRange(oldLoc)) {
    if (myWorld[oldLoc.Row()][oldLoc.Col()].Id() != fish.Id())
    {
      cerr << "illegal fish move" << endl;
    }
    else
    {
      // Put an updated copy of fish in fish's current position.
      Position newLoc = fish.Location();
      myWorld[newLoc.Row()][newLoc.Col()] = fish;
      // If fish moved, empty out fish's old location.
      if (! (oldLoc == newLoc)) {
        myWorld[oldLoc.Row()][oldLoc.Col()] = emptyFish;
      }
    } 
  }
}


Back to Contents     To previous section    MCX1 home     MCX2 home