Saint Louis University |
Computer Science 146
|
Dept. of Math & Computer Science |
Topic: Even More Refactoring
Due:
11:59pm Sunday, November 10
Related Reading: Pages 36 through 42 in case study
Please make sure you adhere to the policies on academic integrity.
We continue with our redesign of the internal flow of control for our simulation steps. To avoid confusion, we will revert back to the original partII code base as our starting point for this assignment. Our new focus becomes implementing what is labeled as "Modification 5" on page 42 of our case study document. In short, we will again change where the responsibility lies for creating a neighborhood of empty cells surrounding a position. This time, we want to have the Neighborhood constructor build such a neighborhood, given the position of interest as a parameter. In this way, the fish class can have a simple line:
Neighborhood nbrs(env, myPos);and the resulting neighborhood will come properly initalized.
The updated UML sequence diagram to reflect the new design is as
follows:
This will be the most technically challenging of the three modifications, in part because of the web of interdependencies that arises among the class definitions. In particular, we now have a Neighborhood class that needs to know about the Environment class to do its job (becuase of the parameter to the new constructor), the Environment class needs to know about the Fish class, and a Fish class that needs to know about the Neighborhood class.
When defining such dependencies, there are two strategies used in C++ to avoid a "chicken-and-egg" situation for defining classes. The first strategy is to only put include statements in the .h file when those definitions are needed for the portions of the class definition that appear in the .h file. In contrast, if you need to use an instance of one class only from within the body of a method of another, you may include the used class's .h file directly within the .cpp file of the class which needs it.
On rare occassions, there will be two or more classes that have an interdepence in the class definition, because classes need to store elements of another as instance variables (such as the Environment class that stores Fish instances within), or because methods of one class use instances of another as parameters or return values. There will be an untenable conflict if two classes genuinely need to store instances of each other (because then there is no way to determine how much memory needs to be set aside for variables of either type). The same issue arises if parameters or return values are sent by value, as this causes copies to be made, and therefore required knowledge about the memory sizes.
However, if an instance of a class can be passed to or from a method by reference (using the & character), then C++ does not immediately need to know anything about that data type, other than that it exists. In this case, circular dependencies can be broken in C++ by using a technique known as a forward declaration. It is possible to use a statement such as
class Foo;to declare that there will be a class named Foo, but without immediately giving a definition for that class. That can be used instead of including "Foo.h" when no further knowledge about the class is yet needed. This technique is already being used in the partII code base in several places. For example, the environ.h file has a statement
class Position;at line 92, so that the Position type can then be used as a parameter and return value in the signatures of Environment methods (when passed by reference).
For this assignment, if careful, you might not need to introduce any of these forward references, but its a technique to keep in mind, and you may vary well run across compilation errors that turn out to be because of circular includes.
We wish for you to start with a clean copy of the "partII" sourcecode. If you are working on turing, execute the following command from within whatever top-level directory you use for this course.
cp -Rp /Public/goldwasser/146/partII modification5
You are to make the desired changes to the source code, and to submit both the sourcecode and a prose document that gives a brief guide to explain what changes you made. I suggest you keep a log of those changes as you go, rather than trying to recall them after-the-fact.
We ask that you also do your best to clean up any remnants of the old design that become deprecated with this change. For example, in the original design, the Fish class has its own EmptyNeighbors method. By shifting that responsibility to a new method in the Environment class, it would be good to remove the EmptyNeighbors method entirely from the Fish class. Please do any other such cleanup that is possible. Also, it would be good to minimize the use of #include statements for the new design, to only those files where they are necessary.
Please ensure that your final result compiles and executes properly.
Your assignment should be submitted electronically (details on the submission process).
Submit the prose document as you normally would.
Given the large scope of modifications made to the multiple versions of the source code, we would like you to create a single "zip" file for each of the modification version. If working on turing, you should execute the following commands. First, from within your "modification5" directory, execute:
make clean zip -o modification5.zip *That will create a new file named modification5.zip that is an archive of your directory (the call to make clean intentionally removes the various compiled files, leaving only the original source code). That zip file is the one that I would like for you to submit online.