Part 2
|
Table of Contents
1. The Story
of C++! (The basics for any beginner!)
2. The A reference document on the basic elements of C++.
3. The Patterns
|
VI. Analyzing How to Fix the
Problems The previous section described three problems with our design of 'ListOfContracts'. First, if we are to display a contract, we need to have access to that contract; second, if we are to modify a contract, we need to again have access to that contract; and third, if we are to display all the contracts, we need to have access to all the contracts without knowing the contract ID's for individual contracts. Two general approaches seem to exist for solving these problems. Note
that the key issue is access to the contracts in the list. One answer
would be to have the class 'ListOfContracts' do all the work. That is, we
could declare the functions 'DisplayInfo', 'ChangeInfo' and 'DisplayAll'
as member functions of 'ListOfContracts'. This would work because any
member function of 'ListOfContracts' has access to the array. However,
many software engineers would be uncomfortable forcing a class whose
purpose is to handle list processing to handle changes to individual
elements of the list. A second solution would be to provide a member function of
'ListOfContracts' that when given a contract ID, returns a contract if one
exists with the desired ID or returns some failure indicator. If we do
this, we will also need a member function to replace a contract after it
has been changed. This solution handles the first two problems because it
provides access to individual contracts but does nothing to fix
'DisplayAll'. We could provide a member function that returns the
contracts one at a time when some function in the main program says
'next'. This would work but is getting quite complex. How about a compromise! The 'DisplayAll' function does seem to be
appropriate as a member function of 'ListOfContracts' since it involves
the whole list. Therefore, the second solution could be used for the
'DisplayInfo' and ChangeInfo' functions while 'DisplayAll' becomes a
member function of 'ListOfContracts'. In other words, 'DisplayInfo' and
ChangeInfo' would remain part of the main program (with 'ListOfContracts'
providing functions to return and replace contracts) while
'ListOfContracts' would take on the responsibility of displaying all the
contracts. Some software engineers might argue that we have arbitrarily
split the functionality here but that is the approach we shall use. It seems then that we have discovered three new
responsibilities for the 'ListOfContracts' class:
Here is a modified CRC card:
![]() Figure 8.3
That finishes the design stage. Hopefully, you are becoming aware of
the fact that this process is not cut and dried and that there are many
ways to accomplish the same goals. As you work on more complex problems
you will find that members of the design team do not always agree about
what kinds of classes are needed, their relationships to each other as
well as their properties and responsibilities. The real world is not so
clean cut. One author of this text remembers spending a whole week-end
negotiating with two associates about the design for a game they were
going to develop. And, even then, the design they came up with was changed
in a year! VII. Back to the Beginning A. Rewriting "common.h" Suppose we include the line:
in both class declaration files. Remember that
the #include preprocessor directive
(see the C++ Essentials text for more information on such directives)
instructs the system to copy the contents of a file into the file
containing the directive. In other words, both class declaration files
will have the code found in 'common.h'. There is no problem here until we
write the main program with '#include' commands to copy in both class
declaration files. Now, all the code found in both files is copied into
the main program which means that the code from 'common.h is included
twice! That file has the lines:
which will result in an error because the constants TRUE and FALSE
would be defined twice! This problem occurs all the time
in C++ so there is a simple solution. You should place another set of
preprocessor directives:
at the top of all '.h' files ( 'dot h' files - meaning files that end
in '.h' and are used as 'include' files).
And, at the bottom of all '.h' files include the line:
Here is 'common.h' with the added code: // A set of basic
type and function declarations common to many programs
When the system sees the new lines it acts as follows: The line
asks the question, "Has the term 'COMMON_H' been defined yet?" The
first time the file 'common.h' is read into a piece of code, the answer is
no and the next line is executed. This line,
defines the term 'COMMON_H'. Afterwards, the rest of the lines are
executed ending with the line
and the system goes to the rest of the code in the file that has
directly or indirectly included 'common.h'. If these lines are encountered again in the file, as they would be in
the case of our main program, the line,
is again processed but this time 'COMMON_H' has already been defined so
the lines from here to the line,
are skipped! Thus, the constants TRUE and FALSE are not declared and
defined again and there is no error. Note the purpose of the '#endif'.
Without it the system would not know how many lines to skip. That is the only change in the file 'common.h'. Again, it is strongly
recommended that you get in the habit of always including the three
lines:
in your '.h' files. Any term can be substituted for 'some_name' but it
is common to use a set of letters made up of the file name followed by _H.
That is where 'COMMON_H' came from in our example. Using such a convention
makes it clear what you are doing and helps you avoid using a similar name
elsewhere in your code. B. Redeclaring the Contract Class
This new code can be viewed under the file name contrct2.h. The file containing the definitions for this class, 'contrct2.cpp' also has some changes: VIII. On to the 'ListOfContracts' Class
A. The Class Declaration The first thing to observe is that the constant
and type definitions have been moved from the file containing 'main' to
here. The main program will no longer have any use for the array itself.
It will only concern itself with the user defined types 'Contract' and
'ListOfContracts'. Being more precise, the programmer for the main program
will have no use for arrays. Remember the idea behind the object oriented
paradigm: Once a class, along with its properties and behaviors, are
defined by some programmer, other programmers can use that class without
caring at all how the internal structure or behavior of that class
is implemented. What confuses people sometimes is that it is possible for one
programmer to act as both the designer and user of a class. However, even
this is typical human behavior. The person who designs an airplane
probably does not think about it as he or she is flying it to visit
grand-ma. As a matter of fact, it is likely that he or she does everything
possible to keep from thinking about the design so as to accomplish other
things while flying. Maintaining awareness of the internal workings of the
airplane - or worse, listening for 'noises' - only makes it difficult for
him or her to focus on other tasks. Similarly, once you as a programmer
are finished with work on a class or a function, it is best to forget
about the inner workings of your work or you will not be able to put your
full attention on the next task. C. Viewing Data Members
as a Structure We have already talked in general about the three data members for this
class. The last one, 'listOfContracts' is an array. We know this from the
code because it is declared to be of type 'ContractArray' - the type we
declared at the top of the file. By the way, note that the class is named
'ListOfContracts' with an upper case 'L' while our array has the same name
except that we use a lower case 'l'. Some people think this is neat,
others feel it just adds to the confusion. You decide for yourself in your
own work. The first data member, 'numContracts' is the counter we talked about
above. When the list is empty, it has the value 0, when one element is in
the list, it has the value 1, etc. (If you would like to see some pictures
demonstrating this, click here.) Note the relationship between
'numContracts' and 'listOfContracts'. The data member 'numContracts' tells
the system which elements of 'listOfContracts' hold actual contracts and
which ones hold garbage. The one other data member 'limit' is not really useful but we included
it because we said we would in earlier discussion. Remember, it will have
the same value as the constant, MAX_CONTRACTS. Later, in an expanded
version of this program, we will see a real value for this data member and
it's relationship to both other data members.
These three data members make up a structure that we can use to
represent a list. Such a structure is only useful, however, if there are
also functions to manipulate it. Developing these is our next
step. D. The Member Functions - Declarations and Definitions
We are now ready for the function definitions so let's move onto
'ch8list1.cpp'.
We now move to the Add member function and explore the role of the data
member, 'numContracts'. This variable has been designed to act both as a
count of the number of elements in the array and as an index into the
array. When the first contract is added, it should be placed into the
zero-th element of the array and note that this is the value of
'numContracts'. Thus the line of code:
This causes the contract in the variable 'contract' to be copied to the
memory location 'listOfCOntracts[0]'. After this, 'numContracts' is
incremented to 1 and the next time an add takes place, the new contract
will be placed in the memory location symbolized by '
'listOfContracts[1]'.
(Be sure that you understand that the values 0 and 1 used here refer to
the values held by 'numContracts' at different times.) Let's step back and go over this function from the top - The first step
is to make sure that there is not already a contract in the list with the
ID of the new contract. (Duplicates can't be allowed if we want to be able
to delete a contract based on it's ID.) The line
gets the ID of the contract to be added and the while loop examines all
the contracts to check for a duplicate. Here we see another use of a
boolean variable. A search can end for two reasons: Thus there are two test conditions in the while loop. Note that 'found'
is set to FALSE to indicate that a match has not been found when the loop
starts. One by one the loop compares the contracts in the list (using
'index' as the subscript into the array of contracts) with the ID of the
new contract (as stored in 'contractNum'). If a match occurs, found is set
to TRUE and this is used to stop the search loop. If there is no match,
'index' is incremented and the next element in the array is used in the
comparison. Eventually, if some contract in the list has an ID equal to
the ID in 'contractNum', the loop will stop with the 'index' acting as an
indicator of which element in the array had the match. If there is no
match in the list, the loop will eventually stop when 'index' reaches
'numContracts'. Note that in this case, 'found' is never set to TRUE. The second step is to insert the contract if no match was found. The
line:
performs the check to make sure there was no match. If so, the contract
is added to the list and the number of contracts is incremented. The rest
of the code simply adds appropriate messages. Delete is somewhat the same as 'Add' but with a few twists. The code
again does a search followed by a test. This time, however, the test is to
see if a match was found and is written as:
The test here is the opposite of the test in 'Add' because the action
takes place only if a match exists - one cannot delete something if it is
not there to be deleted. The other main difference in the code is in the lines:
The way the delete takes place is by shifting down all contracts
starting at the point of the deleted contract.. For this reason the
variable 'i' is initialized to 'index' and not to zero as is usually the
case. For example, if the contract in the fifth array location is to be
deleted, 'i' will be set to the corresponding value in 'index', and the
code will copy the 6th contract (i + 1) into the fifth contract's place;
the seventh contract into the sixth contract's place, etc. In this way the
contract that was in the fifth array element is replaced by the contract
in the sixth array location and all subsequent contracts are also shifted
down one. (Remember, contract ID's no longer have any relationship with a
contract's position in the array.) If you work this out by hand (and you are encouraged to do that), you
may notice that the last contract seems to be in the list twice! Suppose
there are originally 10 contracts in array locations 0..9. The contract in
the ninth place is copied to the eighth spot but there is no contract
copied into the ninth spot. Is this an OOPS? The answer is 'No' and the
reason is the value found in 'numContracts'. It did have the value 10 but
after being decremented it has the value 9. Since this '9' refers to the
next available location in the array, the code, if written correctly, will
see the contract in array location 9 as left over garbage and not part of
the list. This is a subtle point so consider it carefully and review your
own walk throughs and the demo. The member function 'FindContract' uses essentially the same search
code. (Mmmm, do we have a pattern here?) It does, however, take advantage
of the way the 'return' statement works to simplify the while loop test
condition. In the 'Add' and 'Delete modules the variable 'found' was used
as a signal to leave the loop. These functions needed to do something
after the loop - depending on whether a contract was found or not found.
'FindContract' has no further responsibility and thus can stop as soon as
a contract match is found. When a system encounters a 'return' statement it immediately leaves
whatever function it is in. Thus, when the line:
is encountered in 'FindContract', the code leaves the loop in the
process of leaving the whole function. So, if a match is found, the
function stops executing and returns TRUE. (Notice that if a match is
found, this function also places the matching contract into the parameter
'contract' and this contract is then also returned.) As long as a match is
not found, the index variable is incremented and the loop continues. If
the loop terminates, it can only be because all contracts in the list have
been examined. This means that no matching ID was found in the list and
the function returns FALSE. 'ReplaceContract' is yet another example of the same search/action
pattern. This time the goal is to find the location of the contract to be
replaced and then copy into that location the contract passed as a
parameter. The 'else' part of the 'if' statement in the code for this
function is mostly for debugging. Used properly, there should always be a
match. However, one should always code defensively and consider the
possibility of the impossible! The last function 'DisplayAll' is different but very simply. The code
shows us why we decided to make this a member function. It is so easy to
simply walk through the list using a 'for' loop to output the property
values of all the contracts. Note that 'contract' is used here in the way
'index' was used above and that the loop tests for:
instead of comparing ' contract' with MAX_CONTRACTS or 'limit'. The
goal is to display all the contracts that actually exist not the values in
all possible contract locations. E. Variations on a Theme
When we discussed the 'for' statement in
chapter 3, section 5, we saw that it had three distinct parts within the
parentheses with the first part used to initialize any variables, the
second part used to test the stopping conditions, and the third part used
to increment variables if necessary. In the code above, there are two
possible stopping conditions so each is tested in the second part. Note
how the parts are separated by semicolons while separate elements within a
part are separated by commas. Note also that in this example, the variable index is used in the loop
but not declared in the loop. Up to now we have usually written code such
as:
with the word 'int' included in the parentheses. This is simply a
matter of convenience - if one needs a variable for a 'for' loop and the
variable has not been declared, do so when you initialize the variable. In
the example here, however, we are making a quick change to existing code.
In that code 'index' is already declared so let's take advantage of that
fact and not declare it again. It turns out that the 'for' statement is one of the most versatile
statements in C++. We shall see more of this versatility later. A second change we might consider making involves the repeated use of
the same search code in 'Add', 'Delete' and 'ReplaceContract'. You will
see that each of these member functions has the same lines of code:
The first step in this change is to declare our new function, call it
'Search', as private to the class. Here is a fragment of the modified .h
file for 'ListOfContracts' showing this declaration: Regarding the declaration itself: all the searches use the contractID
so it must be received by the function. A careful look at the old code
might lead you to expect that this function should return a Boolean since
all the searches set a variable 'found' to TRUE or FALSE. Unfortunately,
there is a small complication here that won't let us do that. Note that
'Delete' and 'ReplaceContract' both need to know the location of the
contract to be deleted or replaced. If our 'Search' function just returns
TRUE or FALSE, these functions will not know where the contract was found.
Therefore, we will return the location where the contract was found or
return the value in 'numContracts' as an indicator that the contract ID
was NOT found. (Remember, 'numContracts' holds the location of the next
free element of the array of contracts. Thus, if 'Search' returns
'numContracts' it is an indication that all contracts were examined and
none matched.) A look at the definition for 'Search' might help. We will use a 'for'
loop version just to show some variation. Now that we have this function we can simplify each of the functions
that used to do their own searching. Consider 'ReplaceContract': The other two functions, as rewritten, are included below. Now the code is cleaner. Of course, we need to declare and define
the new functions such as 'DisplayContract' and 'AddContract'. Since all
of these work somehow with the list of contracts, they are all passed the
list. In each case, the list is passed by reference for two reasons. One
reason we will not discuss until the next chapter. The second reason
involves writing efficient code. As we have noted, when copying something
by value, one is making a copy of the data being passed. A list of
contracts represents a potentially huge amount of data that would all have
to be copied if the list were passed by value. Passing the list by
reference avoids this copy process and speeds up the code. (By the way,
this is one reason why arrays themselves are always automatically passed by reference.)
In other words, if 'index' equals 'numContracts' we know the search
routine went through the whole list of contracts without finding a
match. IX. Rewriting the Main Program to Use This List Class
A. The Overall Design This might be a good time to review the structure chart for this
problem. There are enough functions calling each other here that a
pictorial representation of their organization might be useful. We start
at the top with the function 'main'. Nothing has changed here from the
version we wrote in chapter 6 and it is quite simple:
'DisplayIntro' and 'DisplayClosingMessage' do not have any function
calls in them so they will not have any boxes hanging off them.
'ProcessUserChoices' is at the heart of the program and it does have a
number of boxes hanging off it, representing the functions it
calls:
If the complexity of the function itself has not made you wonder yet if
this function is too big, perhaps this structure chart has. One way to
simplify this would be to create a separate function for each of the user
choices and have those functions deal with the loops and if statements we
saw in earlier versions of this function as well as with calling the
functions specific to that choice. Here is the simplified version of 'ProcessUserChoices': The code for each of the new functions for handling the options is
simply what used to be under each case. That code can simply be copied to
the appropriate function definition. In addition each function has to have
its own set of local variables to replace the ones such 'contractID' that
used to be declared in ProcessUserChoices'. For example, here is the definition for
'DisplayContract': Inside, the code is exactly the same as it used to
be inside the 'D' case statement except that local variables declarations
had to be added. Again, note that the one parameter, 'contracts' is passed
by reference to avoid copying. The list of contracts could be quite large
and it simply saves CPU time to pass it by reference. Not only is the code cleaner, but the structure chart is simpler. Here
is the partial structure chart for this modified version:
This is much easier to follow. One gets a sense of a cleaner design.
Let's proceed to the next level. A number of the functions call other
functions and our chart could get quite cluttered very quickly. To avoid
clutter we will use a separate chart for each function which we will
further decompose. Remember that each of these charts is really a part of
the larger chart.
We start with 'InitializeInstances'. It is a simple function but it
does call a number of functions over and over:
Note that although these functions are called many times, they are only
listed once in the structure chart. Remember that structure charts do not
indicate the order of function calls or number of times a function is
called. We could rearrange the order of the functions above and the
structure chart would still be correct. Should this part of the structure chart go any deeper? We know, for
example, that the member function 'Add' calls a number of other functions.
However, this is a structure chart for the main program and the inner
workings of 'Add', a member function of the class 'ListOfContracts', is
technically off limits to the code in this file. In other words, at this
level, we are not interested in the 'how' of 'Add'. Therefore, we do not
extend the structure chart to show what 'Add' calls in the process of
completing its function. That finishes the sub-chart for 'InitializeInstances'. We proceed then
to the function next to it under 'ProcessUserChoices'. This is
'GetOperatorChoice' and it has no function calls so we do nothing. Next is
'DisplayContract'. It does have function calls so we could proceed to work
with it. We shall not continue here but leave this as an exercise. The
same for 'DisplayAllContracts' and 'DeleteContract'. X. Summary Of course, the final step is to compile, link, run, and test this code.
Follow the procedure you used in chapters 5 and 6 to get your program
running. The only trick is that now there are three .cpp files to link.
(Borland C++ users can click here to get
further instructions. |
|