| |
Main Menu | Next Section |
V. The Design and Analysis for a Contract Program Using Files
A. Looking for New Classes or Modifications to the Existing
Classes
Olympia has been complaining for some time now that she wants
a program that allows permanent storage of her contract database.
With computers, permanent storage implies file storage. Of course,
adding this capability will require some modifications to the
program. However, most of the program will remain untouched and,
therefore, instead of rewriting the whole program narrative, we
will simply 'amend' it to state:
The conclusion, then, is that we can use the built-in stream classes
to handle file manipulation since these classes have their own
instantiation (create), read, and write (store) capabilities.
The only word in the list that might not fit this analysis is
'database'. However, we have already learned how to link file
names to files and database is strictly another name for our ListOfClasses.
In other words, an instance of 'ListOfClasses' can be seen as
a manifestation of a contract database in RAM while a file holding
the same information represents the same database on disk.
B. Interface Analysis
The only required changes in the program relate to the interface and the functionality
to implement that interface. Up to now, all our interface analyses
have stated that all inputs came from the keyboard and all outputs
go to the screen. This is not the case anymore and we need to
be explicit about where each input comes from and each output
goes. In the case of the contract program, the initial database
data comes from a file named by the user, information for new
contracts as well changes to existing contracts come from a keyboard.
The full database can be written either to the screen or to a
user-named file, while output reflecting modifications to contracts
appears on the screen.
In earlier programs we spent some time designing the look and
feel of the user interface. For example, we examined how input
requests in the form of prompts or menus might look or how output
might appear in columns on a screen. We must go through a similar
process for file input and output. If the file output is ultimately
to be printed for use by human readers, the design process is
similar to that used for screen output. If the output is to later
be read by a computer as file input, the process should focus
on making the input as easy to handle as possible.
In our situation, the contract database will be read back by our
program. Therefore, we need to consider such issues as, given
that a person's name may have two, three or more parts, how should
we indicate the end of a name. The same applies for street address,
city, and state. (Consider Las Vegas and New Mexico vs Bismarck
and Montana). Notice how grammar rules tell us to use a comma
to separate the city from the state in writing out addresses.
Without this, one could interpret "Las Vegas New Mexico"
as referring, for example, to the city "Las Vegas New"
in the state of "Mexico". This gives us a hint - why
not use a delimiter, that is, a symbol of some kind, to separate
the fields of each contract record.
We have been using the idea of delimiter all along without using
the term. For example, the space or blank character is used in
C++ to separate identifiers. That is why we must use the 'underbar,
'_', to connect words if we want them to be part of one identifier
name as in MAX_CONTRACTS. We can't use the space as the delimiter
in our contract database because, as we just observed, names usually
have spaces in them. Therefore, as a somewhat arbitrary but useful
choice, let's follow the idea of using commas. Done! A comma will
separate each field of the output.
We also need to determine the order in which the fields of each contract record will be written. Without this information we cannot correctly write the code to read the data back in. The easiest thing to do is to follow the same order used in outputting contract data to the screen. (This will allow us to partially copy the code from 'DisplayInfo' when we write the function to output contract data to a file.) Thus, the contract ID will come first, followed by the name of the contractee and the street address, city, state, and zip code of the office. After these will come the square footage of the office, the number of desks in the office and number of days per week the office is to be cleaned - with all these fields separated by commas. The end of the information concerning each contract will be indicated by a newline character.
The only other addition to the interface analysis involves a brief
description of the prompts and inputs to allow the user to state
the input and output file names.
on this.
C. Implications for the Program Design
Since our analysis indicates that there are no changes to either
of the classes used in this program, all the changes must take
place in the main program. Previously, we had a function called
'InitializeInstances', whose function was to instantiate a small
set of contracts and place them in the list as the initial database.
Since we are now going to read the contracts in from a file, we
do not need this function. Instead we need a function to read
in the contracts and place them in the list. We could call it
'ReadInstances'. It will have the same single parameter as 'InitializeInstances',
a list of contracts, which it will return once all the contracts
in the file have been placed in it. Notice that we will be able
to take advantage of some of the code for 'InitializeInstances'
so we should not simply erase it.
Many times in these chapters we have observed that there is more than
one way to develop a program. Some software engineers promote
an approach based more on prototyping than on careful,
systematic pre-analysis and design as has been described in this
text. The prototyping approach suggests that one should start
with some analysis and design but be willing to move sooner
into the coding stage to try out one or more designs. The idea
is to experiment with specific designs, perform a careful analysis
of the results of the experimentation, and be willing to throw
out what does not work. Key to this approach is:
Some students, when they hear this, claim they want to try this
approach. However, all they often mean is that they want to get
down to coding without ANY analysis and design. And, they often
ignore the other side of the prototyping approach, the need to:
Although we have not really been acting as prototypers here, some
of the results are the same. At this point we have code that works
in some ways but still needs to be improved. We could see our
results so far as indicating that most of the code works but that
we need to change that aspect dealing with input and output of
the database itself. The point being made here is that we, therefore,
know that we can keep most of the code and even that the part
that needs to be changed may have elements worth salvaging.
Having said all that, let's review what needs to be modified to
allow the program to output the database to a file when the user
indicates that he or she wants to quit. Previously, when the user
chose to end the program, nothing happened except for the displaying
of a closing message. That indicates that, unlike when reading
in the database - where we could modify an existing function,
some new functionality is needed here. On the other hand, this functionality
is somewhat similar to that used to display the database on the
screen, so maybe we can borrow some of the code used there.
Where can we cleanly fit in this new functionality? Well, every
other user option handled in the function 'ProcessUserChoices'
except for the 'quit' option calls a function. Why not have a
call to the function that will handle the output placed in the
code for the quit option! The other possible choice would be to
include the code inside 'DisplayClosingMessage', but the very name
of that function discourages its use in this way.
We will call this new function 'OutputData'. Does it receive or
return anything? Remember the design pattern for output functions we developed long
ago ? We determined there that output functions
receive one or more items - the data to be output - and return
nothing. Since it is the list of contracts that is to be output,
this analysis implies that it is the list that should be received.
Since we have not written a function quite like this before, it
would be wise to work a bit on an algorithm before proceeding
to code. As you probably have already observed, however, new functions
don't usually develop in a vacuum. They can often be built by
borrowing ideas from elsewhere. We have already noticed that we
might want to borrow from the code for 'DisplayAllContracts' since
it matches some of the functionality of this new function - it
outputs all of the contracts, even if the output goes to the screen
and not to a file. Also, in the previous section we studied file
output. So, let's see if we can't put together an algorthm that
borrows from both these sets of code.
Reviewing the code in the file 'fileio.cpp, we see that
we need get the file name from the user, instantiate a stream
connected to that file, and check the results of the instantiation
process. Examining 'DisplayAllContracts', we see that one way
to accomplish the rest of our goal is to iterate through the list
of contracts and call a function to output each individual contract.
Our algorithm might then look as follows:
Get file name from user Attempt to instantiate output stream using this file If the instantiation fails display error message exit Else Reset the List to begin iterating from the beginning While there are more contracts in the list Get the next contract from the list Output it to the output stream for the file End while End ifThat's it! Note that this algorithm uses the world 'while' to indicate a loop. That does not mean that we will use a 'while' instead of a 'for loop in the actual code. It is just that in English one does not commonly use the word 'for' to indicate repetition. What looping statement we use in the code depends on what works best and on personal preferences. We have seen that in most cases the 'for' and 'while' are semantically interchangeable.
Writing that algorithm, proved relatively simple but useful. It
would be wise if we also wrote an algorithm for 'ReadInstances'
since this too is a type of code we have never written before.
We know we need to ask the user for the file name where the database
was stored and open a stream attached to that file. Thus, this
algorithm will begin much the same as the previous one.
Back in chapter three you learned the algorithm for reading data
from a keyboard. The algorithm for reading from a file is much
the same except that now the stopping condition is the end of
the file. Here then is the algorithm:
Get file name from user Attempt to instantiate input stream using this file If the instantiation fails display error message exit Else While there are more contracts in the file Get the next contract from the file Add it to the list End while End ifWe will discover, when it comes time to implement this algorithm in code, that there are a number of little details to be handled. One, of course, is reading in all the individual fields of each person's record. While such details are important, they are not part of the algorithm, since they are related to the idiosyncrasies of C++ input. This algorithm does, however, represent another general pattern to be used whenever the goal is to read from a file. Be sure you understand it.
| |
Main Menu | Next Section |