Previous Section Main Menu Next Section

CHAPTER 6

GIVING OLYMPIA A BETTER INTERFACE
OR
USER INTERFACE DESIGN - SOME PRELIMINARY TECHNIQUES
/B>

Section III: C++ Code Section I: User Interface Analysis - Giving Olympia Choices Section II: Functional Decomposition of 'main' Section IV : Putting It All Together

Section III. C++ Code
Let's try our hand at writing the C++ code for the algorithm for 'ProcessUserChoices'. We can do this even though we have not examined closely any of the functions that this algorithm uses. While designing and writing 'ProcessUserChoices', you should not care or concern yourself at all with HOW any other functions work. Simply assume that they do work as they are supposed to - in other words, assume that when the time comes, you, or whoever is assigned the task, can write them correctly.

This mind set - that I don't care about anything other than what I am working on right now - is often hard for beginners. They often worry about the details of some other part of the program instead of focusing on the part they are presently working on. It may be true that the code presently under consideration will not work if some other part is coded incorrectly and it may be true that that other part involves some programming challenge but worrying about that other part only makes the present code more difficult.

A. Defining "ProcessUserChoices" and the Built-in "bool" Type
The declaration for 'ProcessUserChoice' is simple, given that there are no receives and returns:

void ProcessUserChoices();

Writing the definition for this function is where the challenge is. The first issue involves the boolean variable. The most recent standard for C++ includes the 'type' bool to handle boolean variables. Such variables can only hold the values 'false' and 'true' which are also built into the system. To declare a variable called 'done' of type 'bool', one simply writes:

bool done;

Once this line is written, one can then include instructions such as:

done = false;
or
done = true;

One can also initialize a Boolean variable, just as one initializes a variable of any other built-in type. For example:

bool done = false;

The algorithm for 'ProcessUserChoices' shows that the variable 'done' is initialized to false so we can use the code we just wrote above in 'ProcessUserChoices'. The next step is to declare the five instances of 'contract' just as we did in the first version of this program. This must be done here because it is no longer done in 'main'. Actually, we could have kept the declarations in 'main', but then we would have needed five parameters for this function - one for each of the instances. (Remember, this function cannot work without these instances.) You, as programmer, have a choice - you can declare the instances here in 'ProcessUserChoices' or declare them in 'main' and pass them to 'ProcessUserChoices'. Since 'main', as presently designed, has no use for these instances, it is better to declare them here.

There may be more variables to declare, and, as we discover them, we will add their declarations. Here is the code so far:

B. Using typedef to Create a Boolean Type
(This section can be skipped on first reading but should be read before going onto the next chapter)

Only the most recent versions of C++ compilers (as, for example, Borland C++ 5.0) have implemented the built-in Boolean type. This means that the programmer must declare his or her own boolean type, if the code is to work with earlier versions of C++. You will often find code that uses an earlier method for creating Boolean variables. In this method the programmer must declare his or her own boolean type. As you know, you have already been declaring your own types in the process of declaring new classes. The boolean type, however, does not have the complexity of say the 'contract' type - with a complex data structure and specific functionality - so we do not need to or want to use the class declaration mechanism to create the boolean type. C++ provides a much simpler mechanism, the typedef.

Here is the code:

Upon seeing the line

typedef int Boolean;

the compiler accepts the fact that there is a new type called 'Boolean' which at this point is nothing but a nickname for the type 'int'. (We will use the type name 'Boolean' to guarantee that we do not cause troubles with any compiler that already knows about the type 'bool'.) We then create two constants of this new type with the values 1 and 0. The constant 'FALSE' has the value 0 to match C++'s definition of 'false'. The constant 'TRUE' has the value 1 to match C++'s definition of 'true'. Two things to note:

  1. The built-in version has 'false' and 'true' in all lower case while this version uses an all uppercase format to go along with our rules for writing constants.

  2. C++ does not actually enforce the restriction that Booleans hold only the values 0 and 1 because variables of type 'Boolean' really can hold any integer value. (For the built-in 'bool' type, the identifiers 'false' and 'true' have been similarly defined. The only difference is whether you, as a programmer, create a new type yourself or simply use the built-in type.)

The three lines

declare a type. Just as with classes as types, this new type can be used anywhere in the whole program - even in class declarations and definitions. Many argue that as a new type declaration these lines should be in their own file. It is common for programmers to create a file that has in it certain basic type and function declarations that they, the programmers, find useful. Let's create such a file and call it 'common.h', For now it will only have this boolean declaration. Later, we may add more items as we discover them. Note that this is different from the files for the class 'contract'. These files may be used in other programs involving contracts but, in general, contracts are not useful in programs so these files will not be used often. On the other hand, the boolean type is useful in almost all programs and the file declaring it will often be included in programs.

Here is the code for the file "common.h" as built so far:

Of course, we have yet to declare a variable of type 'Boolean'. Remember, as we said earlier, there is a difference between declaring a type and declaring a variable. Type declarations simply present a description of a new concept. They do not declare that anything of that new type actually exists. It is a variable declaration that states that a new thing actually exists. Thus, no element of type 'Boolean' exists until the programmer adds a constant declaration as above or a line such as:

Boolean done;
or
Boolean done = false;

This last line could be used in place of the line

bool done = false;

in the code just written for "ProcessUserChoices".

C. The 'if' and 'switch' Statements
Now we proceed to implement the algorithm - with many of the lines being almost copies of those found in the algorithm. Here are the first two:

bool done = false;
while (!done)

Notice the simplicity of the test in the second line - it can be read as "while not done....". Observe that there is no need for a relational operator such as '==' or '!='. We could have written

while (done == false)
or
while (done != true)

but these have the same meaning as
while(!done)

and this new approach certainly is simpler. Besides, it shows that we know what we are doing!

Inside the loop we start with the line:

choice = GetOperatorChoice();

'GetOperatorChoice', of course, is one of the functions we have decided we needed but which we have yet to code. As we have seen before, there is no problem, at the coding level, with calling an undefined function as long as we write the definition later.

Notice that 'GetOperatorChoice' returns a value that is stored in a variable called 'choice'. Since this variable has not been declared, we better do so now, before we forget. Our analysis of 'GetOperatorChoice' indicates that it returns a character, so we declare 'choice' to be of type char:

char choice;

The next part of the algorithm contains a rather complex set of embedded 'if' statements. A direct translation of this would look as follows - with code added to handle the possibility that the user has entered his or her choice in lower case letters:

By now you should be used to seeing 'or' statements like this. The only point to observe is that we have two new variables to declare - 'contractNum' and 'answer'. The function 'GetContractNumber' returns an integer so clearly the variable should be of type 'int'. We will declare it right after the line that declares 'choice'.

char choice;
int contractNum;

The variable 'answer' clearly will hold a 'Y' or an 'N' so it should be of type 'char'. There is nothing new there. But, what is somewhat new is where 'answer' is declared. Up to now, we have talked about local variables as being variables declared inside functions. Actually, a variable can be 'local' to any block of code - where a block is any part of program between a matching set of curly brackets ({…}). Since the variable, 'answer', is only used in the block that deals with a users choice of 'Q', it might be nice to make this variable local to this block. That is what we did below.

	if ((choice == 'Q') || (choice == 'q')) 
        {   cout <<  "Do you really want to quit, (Y) or (N)\n";
            char answer;
            cin >> answer;
	    if ((answer == 'Y') || (answer == 'y')) 
	    {	done = true;
	    }
        }
Be aware that we could just as easily have declared 'answer' up with the other variable declarations. The only difference would be that then 'answer' would be available to the whole function and not just to the one block.

Once these declarations are added, the code works, although it takes a bit of effort to write and is not easy to read. To handle situations such as this, where there are a number of choices, C++ has a new statement - the switch statement. It does not add any power to the language - whatever one can write using a switch statement, one could write using nested (embedded) if's - but it does make the code easier to read and write. It is an example of what some people like to call syntactic sugar - because it provides no extra power but makes the code 'sweeter'. Here is how the above code looks using this new statement:

Quite a mouth full of new code! However, knowing what this code is supposed to do and understanding how an 'if' statement works, should make this code easy to understand.

The line:

switch (choice)

tells the compiler to concern itself with the contents of the memory location symbolized by the variable 'choice'. Each of the 'case' lines tells the system what to do when this variable ('choice') has a certain value. For example, when 'choice' contains 'c', the code executes the lines:

In essence, "switch(choice)" acts as the first part of an 'if' test condition and can be translated as "if (choice ==....)". Each case statement fills in the right hand side of this relation. Thus, "case 'q'" fills in the relation so that it reads, " if (choice =='q')".

OK, but what is this new break instruction? The 'switch' statement has an interesting characteristic in that, unless told otherwise, ALL lines below a certain 'case statement' are executed until the end of the switch. In other words, if the break statement is not there at the end of the instructions to be executed when choice = 'c', the system will also execute the lines for the 'q' case and the 'default' case. It is the break statement that tells the system to skip to the end of the switch statement - the line with the comment "// end switch".

This is why the lines containing only the word 'case' followed by some character, such as:

case 'Q':

work. At first glance, you might think that the code is saying that NOTHING is to be done when 'choice' equals 'Q'. But, since there is no break statement here, the system 'falls through' and executes the lines corresponding to the "case 'q'" line until it reaches the break statement at the end of this case. Then it skips to the end of the switch statement. In essence, you can read:

case 'Q':
case 'q':

as being equivalent to "((choice == 'Q') || (choice == 'q'))".

The next new element is the default statement. The original algorithm did not bother to handle the situation where the user enters some invalid character- in this case, neither 'd', 'c' or 'q'. We will probably code 'GetOperatorChoice' to handle this, using the "Clean Handling of Invalid Data" pattern, but it is common practice to also have the switch statement handle invalid options. If 'GetOperatorChoice' is coded correctly, the code we write in the switch statement will never be used but such redundancy does not hurt and since, as we can see, the switch statement makes it easy to handle this situation, why not do so.

What the default statement signifies is that, if none of the case statements above the default statement is executed, the default statement itself will be executed. In other words, if the system does not enter one of the cases and hit a break statement causing the system to jump to the end of the switch statement, the code inside the 'default' statement is executed. In most situations, as in our situation, the default code simply announces that an invalid choice has been made. Note that there could be, but need not be, a 'break' statement here. We are already at the bottom of the switch statement, so if this option is taken, there is no code below that should not be executed.

Let's review how this whole section of code works. Having set 'done' to false, the system automatically enters the 'while' loop. Then the user is asked to enter a choice and that choice is compared with the various options. If the user wants to display or change a contract, the contract number is requested from the user and the appropriate action is taken. If the user enters 'q' to quit, he or she is asked to confirm that choice and if this choice is confirmed, 'done' is set to true.

As with all while loops, when the bottom of the loop is reached, the code jumps to the top and executes the while loop test again. If 'done' is true, the loop ends and the system jumps to the first line of code after the loop. Otherwise, the system enters the loop again, causing the switch statement to execute. Note that the switch statement, like the 'if' statement, is a one time deal by itself. But, if it is coded inside a while statement, it can be executed over and over. It is the 'while' that causes the repetition, not the 'switch'!

You might want to consider the fact that we have another pattern here - one that is especially useful when there are a number of choices a user can make and each choice involves different code. This is the second new pattern in this chapter. Just a reminder: Take advantage of patterns like this whenever possible. It will make your coding life easier.

D. Passing a Class Instance as a Parameter
There is a problem with this code that we have ignored so far. If you review what the design for the functions 'DisplayInfo' and 'ChangeInfo' say these functions are to receive, you will discover that they are to receive a contract, that is, an instance of the class 'Contract'. The code we have written, however, passes an integer, indicating which contract is to be used. Somehow, we need to turn this contract number into a specific contract. Given what we know so far, the easiest way to handle this is to again use a switch statement - nested inside the switch statement handling the choices.

'DisplayInfo' and 'ChangeInfo' will be handled the same way so here is the code to call 'DisplayInfo':

As you can see, instead of simply calling 'DisplayInfo' after the contract number is entered by the user, the code uses 'contractNum' as the value to switch (branch) on. If 'contractNum' has the value 1 (the integer value 1, not the character value), the code calls 'DisplayInfo', passing contract1, which is the symbolic name for the first contract. The same is true for the rest of the contracts.

Note that one can use the switch statement with both integers and characters. As a matter of fact, the material inside the parenthesis of a switch statement can be any expression that returns a single value. Technically, one could write something like:

switch (num1 + num2)

and it would be valid as long as num1 and num2 were integers or could be coerced into integers. By the way, when a variable of type 'char' is in the parenthesis, it is technically turned into an integer and treated as an integer code.

************************

We finally have code that works, but there is one potential improvement we can make. Suppose the user wishes to display the information on many contracts. As we have written the code, this user will have to re-enter a 'D' and then the new contract number. That's OK, but some might argue that a user displaying contracts will often want to display many contracts at once, and that being forced to repeatedly re-enter a 'D' is a sign of poor design. To avoid this, we could add a loop that allows the user to enter contract numbers until a zero is entered as a sentinel value. Our interface analysis did not discuss this, but now that we are dealing with implementation details, the value of this is clearer. Here we have an example of the interplay of analysis, design, and implementation.

What pattern would one use to implement this change? Take a moment to answer this.

 

??????????????????????????

 

Once again we would use the "While Loop With Priming Read" pattern. Now the code code (for the 'DisplayInfo' option) would look as follows:

Notice the complexity of this code. We have an outer while loop (not shown), inside of which we have a 'switch' statement. Inside that we have 'while' loops and embedded switch statements for both the 'display' and 'change' user options. (The exact same while-switch code will be used to handle the user's wish to change a property. Click here to see the full code for this function.)

The code here really is on the verge of being too complex. Later, we will see how 'ProcessUserChoices' can be simplified via further functional decomposition.

E. Receiving Class Instances as Parameters
Our analysis indicates that 'DisplayInfo' receives a contract and returns nothing. Therefore the declaration looks as follows:

void DisplayInfo(Contract contract);

Note how we use 'Contract' (the name of the class) where a type name goes. This is another reminder that class names are also type names. Thus, we are declaring 'contract' (lower case 'c') to be an instance of class 'Contract' (upper case 'C').

You might be concerned that this instance declaration does not include any initialization of property values as have the other Contract declarations in this chapter. However, Since we are declaring a parameter here and the system knows that this local instance will be immediately instantiated with the property values of some contract on the calling side, this usage is OK. What actually happens is that the properties of the actual parameter are copied to the corresponding data members in the local instance.Later we will see situations in which it is necessary to explicitly declare and define what is called a copy constructor

The definition is relatively straightforward. We simply need to request each piece of information from the instance by calling the appropriate member function and then display it. Recall from the last chapter that we call a member function by typing the instance name, followed by a dot (.), followed by the member function name. Here then is the code:

We have added three 'endl' outputs at the end to cause three lines to be skipped on the monitor between this output and whatever happens next. This simply makes the output more readable.

There is a slightly shorter way to write this code. Functions that return values can be placed anywhere a variable can be placed, as long as one is careful to match types. For example then, one could drop the variable 'sqFootage' and replace it directly with the function call as in:


   cout << "\n\nThe square footage for the contract is: "
        << contract.ProvideSquareFootage()
        << endl;

Using this approach, the value returned by 'ProvideSquareFootage' is sent directly to the output stream, instead of being temporarily stored in a local variable whose value is then sent to the stream. You should use whichever approach feels best to you. (Note that in this version the function call does not end with a semi-colon because it is part of a larger instruction.)

F. Returning a Class Instance as a Parameter
The declaration for 'ChangeInfo' is different from that of 'DisplayInfo' in that 'ChangeInfo' returns a contract. Therefore, we could write the declaration as follows:

Contract ChangeInfo(Contract contract);

If we did this, however, we would have to rewrite the calls to "ChangeInfo' in 'ProcessUserChoices' to, for example:

contract1 = ChangeInfo(contract1);

In other words, we would have to pass 'contract1' via the parameter list and get back the changed contract via the function return mechanism.

While this would work, it is not as commonly done as a second way. In chapter four we learned how to use the '&' symbol to allow more than one value to be returned. We noted further that the '&' symbol creates a "two way transmission path", allowing information to not only be returned but also sent via the same connection. In the case of 'ChangeInfo' we already need a path to transmit information from the calling function (in other words, we need to receive a contract) so why not simply use the same path to return the changed contract. The '&' does this. Here is the new declaration:

void ChangeInfo(Contract& contract);

To emphasize the key point: The '&' symbol creates a two way path so that the original information about the contract in question can be received by 'ChangeInfo' and the changed information can be returned. When a call to 'ChangeInfo' such as:

ChangeInfo(contract1);

in 'ProcessUserChoices' is completed, the information originally in contract1 has been sent to 'ChangeInfo' and the same information, as modified by 'ChangeInfo', has been sent back. The instance 'contract1' in 'ProcessUserChoices' has now been modified.

 
We now move on to the definition for 'ChangeInfo'. This time, instead of calling 'Provide.... member functions as we did in 'DisplayInfo', we call 'Change....' member functions to cause the properties to be changed. Before doing that, however, we must get the new value(s) for the property(ies) to be changed.This, in turn, requires that we ask the user which property(ies) she or he wants changed. To make the user interface more efficient, we allow the user to change as many properties as she or he wants - using a sentinel value of 'N' to indicate completion. Here is the code:

Again we have a while statement using the basic pattern we have seen over and over again, inside of which there is a switch statement to handle changing the various contract class properties. In each case, except the default, we ask the user for the new value of the property chosen to be changed and then call the appropriate member function to accomplish the change.

One other point: The while test condition is a bit different in that it uses the 'AND' symbol ('&&'). Students are often confused about when to use AND and OR. The confusion comes partially from the way a situation like this is commonly described in English. The specifications are likely to say something like:

"Stop when the user enters 'N' or 'n".

While the specifications statement has an 'or' in it, it gets translated as 'AND' or '&&'. The reason is that the while statement is not concerned with when to stop as when to continue. Remember, the while statement test condition can be translated as:

"while the following is true execute the following.....".

In other words, when translating a phrase such as "Stop when the user enters 'N' or 'n'" into a C++ while statement, it might be wise to first translate the English into, "while the user has not entered 'N' and has not entered 'n'". Note how this English captures the meaning of the C++ 'while' statement and, therefore, the translation process (from algorithm to code) is relatively straightforward. The moral of the story is: Be very careful how you translate English 'and' and 'or' into '&&' and '||'.

Since this translation process can be quite tricky, here is another way to handle it. Above we noted that the specifications usually describe the conditions under which a loop will stop. We then observed that a 'while' loop is coded in terms of the conditions under which the loop will continue. Continuing is the opposite of stopping so we can apply something called DeMorgan's law. If you have studied this law, you might want to remind yourself about what it says. However, you don't need to know anything about it to apply the algorithm below:

  1. Translate the stopping condition into C++. For example, if the stopping condition is "Stop when the user enters an 'N' or an 'n'", write the code:
    (choice == 'N') || (choice == 'n')

  2. Turn the '||' into it's opposite, that is into '&&'
    Turn '==' into '!='.
    The code now looks as follows:
    (choice != 'N') && (choice != 'n') This is the correct code!


    Topics Covered in the "Essentials of C++"
    Typedef
    The Switch Statement

    Example code involving boolean variables
    Example code involving the switch statement

    Top of Section Main Menu Next Section