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.
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
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 functions 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 wouldnt 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