| |
Main Menu | Next Section |
| Section II: Functional Decomposition of 'main' | Section I: User Interface Analysis - Giving Olympia Choices | Section III: C++ Code | Section IV : Putting It All Together |
A. Discovering a Set of Functions
Back in chapter four, we explored how to decompose a problem in order to modularize it and simplify the design of 'main'. We need to use that methodology again here because the issues we are focusing on have little to do with the 'Contract' class and much to do with the program that uses that class. (With systems involving truly complex user interfaces, the interface itself may become a class with one or more instances.) The question then is, what functions might this program need that do not belong to a specific class.
The Interface analysis does help us here. The tasks referred to there included:
We do not yet know whether users will be asked all of the questions in 'main' or elsewhere. Should we have 'main' request both which contract to work with and which operation to perform on that contract, or should we only have 'main' ask which operation the user wants to perform and have the functions 'DisplayInfo' or 'ChangeInfo' ask which contract? Let's explore the first way in this example.
Now, do any of these functions need further decomposition? The 'intro' and 'closing' functions as well as the 'Get...' functions are probably simple enough - we have already worked with these kinds of functions and found them to not be difficult. How about the other functions? The function 'DisplayInfo' will send messages to the instance of contract under consideration seeking the information it needs to display. In this sense it uses or calls other functions. However, these are functions already designed and implemented as part of the class design we did in chapter 5 so we have no further design work to do.
'ChangeInfo' will likewise use member functions of the class 'contract' but it has some added tasks. First, it needs to find out which property(ies) the user wants to change. Therefore, we need a function to ask the user which property(ies) he or she wishes to change. Of course, we already have such a function. What we are discovering here is that 'GetProperty' is used by 'ChangeInfo'.
'ChangeInfo' also needs to ask the user for the new value of the property being changed before it calls the member function to make the change. Again, we have such functions in 'GetSquareFootage', 'GetNumDesks, and GetNumDays' . Our conclusion is that these too are subtasks of 'ChangeInfo'.
Here is a structure chart of this program as designed. There are two points to note: First, the member functions used in the program are not included in the chart. Second, since structure charts are a general tool, not designed specifically for C++ or any programming language, we use a more English like naming scheme for the modules.

B. Functional Design
Having determined a likely set of functions, it is time to work on the Purpose/Goal, Receive, and Return analysis for each function. Here we shall look briefly at two functions, 'DisplayInfo', and 'ChangeInfo'. The purpose of these is straightforward - to display the values of the properties of a contract or change the value(s) of one or more properties of a contract.
Now, what do these functions need from the function(s) that call
them and what will they return? Consider 'DisplayInfo' first. From our earlier discussions
of functions that output values, we know that this function needs
to receive the values to be output. In the case of 'DisplayInfo'
there are four such values - the square footage, the number of
desks, the number of days, and the per week charge. We could then
say that this function has four receives but another way to answer
this is to say that the function simply needs a contract since each
contract 'contains' these four pieces of information. The advantage
of this new approach is that it allows new properties to be added
to the contract design or old properties removed without changes
to the parameter list. If eventually contracts acquire more properties,
the values for those new properties will still be provided to
'DisplayInfo' as long as the contract itself is provided.
In terms of 'returns', we know that functions that output values
usually do not return anything and 'DisplayInfo' is no exception.
Here then is the design for this function.
DisplayInfo
Purpose: To display the values of the properties of a contract
Goal: The monitor contains the values of the properties of
a contract
Receives: A contract
Returns: NONE
The 'receives' for 'ChangeContract' are the same. This function
cannot change a contract unless it has one to change. However,
the situation is a bit more complex in the case of 'returns'.
Whichever function calls this function would, presumably, like
to get back the changed contract. As a general rule, whenever
a function changes values, it probably should return what got changed.
So, 'ChangeContract' both receives and returns a contract. The
design looks as follows:
ChangeInfo
Purpose: To change the values of one or more of the properties
of a contract
Goal: The values of one or more of the properties of a contract
have been changed
Receives: A contract
Returns: A contract
C. An Improved Decomposition
It turns out that there is one further modification we could make to our general design. As noted, the program we are working on should allow a user to continually select a choice of operations until he or she is finished. This is a very common, basic program requirement - one that is often handled by a separate function. In Chapter 4 we briefly discussed the idea of keeping 'main' as simple as possible. Given this, let's shift all the functionality we have discovered above, except for that of displaying the introductory and closing messages, to a new function called 'ProcessUserChoices'. This, in turn, modifies the structure chart:
The purpose of 'main' is now very simple:
The algorithm then looks as follows:
D. Top Down or Bottom Up Design
There are a number of ways we could proceed. We could focus our
attention on some of the simpler functions at the 'bottom' of
the structure chart and work our way up to the top, designing
as we go. This is called bottom up design. Or, we could
start at the top and work down - top down design. Most
programmers prefer one of these two approaches but are not rigid
about it and jump around as needed in what has been called, for
the fun of it - middle out design. Let's use the top down
approach here but jump around when it seems best to do so.
Our task then is to design the algorithm for' ProcessUserChoices'
- the first function not yet designed starting from the top of
the structure chart. It's purpose, as we started to describe above,
is to:
Goal: The user's requests have all been handled
As for receives and returns, ' ProcessUserChoices' does not need
anything from 'main' nor does it return anything to 'main'. This
is clear when you look at the algorithm for 'main'. It simply
handles the introductory and closing screens and passes off the
real work to ' ProcessUserChoices'.
E. Boolean Variables and Another Pattern
We now move onto designing the algorithm for 'ProcessUserChoices'. You may recall from
chapter 3 that the purpose statement
can be used to determine if any patterns can be used to assist
in the design of a function's algorithm. The purpose statement
here certainly implies a loop and a quick review of the loop patterns
turns up a pattern that continues until a user enters a sentinel
value - the "While Looping With a Priming Read" pattern.
This sounds like what we need so let's explore its usage here
- using 'Q' as the sentinel value the user is to type in when
finished..
Get choice
While choice != 'Q'
{ GetContractNumber
if choice = 'D' then
{ DisplayInfo(contract number)
}
else
{ if choice = 'C' then
{ ChangeInfo(contract number)
}
}
Get choice
}
done = FALSE
While not done
{ Get choice
GetContractNumber
if choice = 'D' then
{ DisplayInfo(contract number)
}
else
{ if choice = 'C' then
{ ChangeInfo(contract number)
}
else
{ if choice = 'Q' then
{ output "Are you sure you are done, Y or N
read answer
if answer = 'Y' then
{ done = TRUE
}
}
}
}
}
Notice where 'done' is set to TRUE. Inside the loop we have a
new option - when 'Q' has been entered for 'choice'. When this
happens, we ask the user if he or she is sure they want to exit.
If they say 'yes', 'done' is set to TRUE and the loop will stop
executing. If the person says 'no' (or anything else for that
matter), 'done' is not changed, that is, nothing happens, and
the loop continues.
So far so good, but there is a problem. Before proceeding, see
if you can find it, first by simply reviewing the algorithm and
then, if the problem is not obvious, by tracing it.
What do you think the problem is? If you said that users are forced to enter a contract number even if they have entered 'Q' to signal that they are finished, you are correct. If you said something else, be sure you see the problem now- perhaps by tracing the algorithm again.
To fix this we need to move the call to Get Contract Number inside
the two 'if' statement branches that need a contract number -
when the person types a 'D' or a 'C'.
done = FALSE
While not done
{ Get choice
if choice = 'D' then
{ GetContractNumber
DisplayInfo(contract number)
}
else
{ if choice = 'C' then
{ GetContractNumber
ChangeInfo(contract number)
}
else
{ if choice = 'Q' then
{ output "Are you sure you are done, Y or N
read answer
if answer = 'Y' then
{ done = TRUE
}
}
}
}
}
| |
Main Menu | Next Section |