Chapter 3 : Interacting Classes

Chapter 3 : Interacting Classes

Contents

3.1. Introduction

Abstract Data Types, implemented as C++ classes, define our approach towards designing and building large systems. Clearly, a large system might involve a combination of many classes and complex classes may be constructed out of simpler ones. This chapter explores these so called "using relationships" among classes and introduces some features of C++ that are necessary to support them.

The chapter also gives some further insights into how to analyse complex problems and approach the design of modular programs.

The chapter is structured around the example of a simplified cashpoint machine which allows users to carry out transactions on bank accounts. In turn, the bank accounts make use of the Money class defined in the previous chapter.

3.2. Simple analysis of the Cashpoint

The goal of our example is to simulate the operation of a cashpoint machine. In order to make the problem manageable, we will make some major simplifications to the real-world scenario.

The first, and often most difficult, stage of solving a problem like this is to analyse the task and derive an overall program structure. In the last chapter we discussed an approach towards designing class interfaces based around five categories of method (interface function). However, this assumes that we already know which classes we want. In this example we start a stage further back - we need to identify the correct classes for our program before we can specify their interfaces. In other words there is an analysis stage before a detailed design stage.

As with the last chapter, there is no magic formula to be applied. However, as a starting point we can try writing a brief summary of what the cashpoint simulator does. The summary consists of short sentences with all "nouns" and "verbs" highlighted (in this case using bold and italicised type respectively).

  • The Cashpoint allows users to access their accounts.
  • They may withdraw money or request a balance.
  • Each Account has an account number and a Personal Identification Number (PIN).
  • These are Checked when the user logs on.

    The nouns suggest possible classes, data members, function arguments and results for the program (cashpoint, user, account, money, balance, account number and PIN).

    The verbs suggest possible methods for classes (access, withdraw, request-balance, check).

    The next stage is to decide which nouns represent classes (i.e. types of general object) and which represent data members of these classes. My decision was that Cashpoint, Account and Money look like classes; balance, account number and PIN look like data members of account; and that the users are something outside of the program.

    Let us now write a brief description of each class and identify some initial methods.

  • The Cashpoint class provides access to a number of bank accounts. The class supports access to accounts, withdrawal of money and balance requests.
  • The Account class specifies a single bank account. It maintains data about account numbers, PINs and the current balance.
  • The Money class we already know.

    Of course, this analysis is very basic. The following design stage will involve fleshing out each class with a complete complement of methods and data members to make it sufficiently general to solve the problem (and also to potentially be re-used in future problems).

    We have already seen the design of the Money class. The rest of the chapter looks at the design and implementation of the Account and Cashpoint classes.

    3.3. The Account Class

    Let us briefly apply the five categories of interface function to the Account class:

  • Constructors - initialise with zero balance.
  • Combination - none appropriate.
  • Access - set and return the account number, PIN and balance. The balance is manipulated through two functions - credit and debit.
  • Tests - verify given PIN and account numbers.
  • I/O - print out the account details.

    This leads to the following definition file (Account.h).

    Notice that the Account class contains a data member of type Money. Here we see one possible relationship between classes - one class can be built on another. Note also that the Money class appears in arguments and results of Account's methods. This means that any program using the Account class should know how to use the Money class as well.

    The design carefully ensures that the PIN number is only released outside the class once, when it is first allocated (so the account owner can be told what it is). It is allocated internally and can be checked, but not read, by other programs. Here we are using the encapsulation property of classes to increase security.

    Here is the implementation of the Account class.

    Several things need to be explained about this implementation.

    The function allocate_pin() allocates a random pin number in the range 1000 to 9999. It uses the standard function rand() to generate a random number in the range 0 to RAND_MAX (usually 32767) and then modifies this to get it in the desired range (note how the remainder operation, %, is used). If you want to use this function in your own programs you need to include the header file <stdlib.h>.

    See how the credit and debit functions call methods on the balance object (and also how debit won't let the account go overdrawn).

    3.4. Constructors for Nested Objects

    Look at the constructor function for Account. It has to initialise data members including bal, an object of class Money. This requires special treatment - the constructor for the data member bal is called directly after the function heading before its main body.

    In general, THE CONSTRUCTOR FOR A CLASS HAS TO EXPLICITLY CALL THE CONSTRUCTORS FOR ANY CONTAINED CLASSES. These calls are made directly after the function heading (following a colon) and must take place in the order that the data members are declared in the class definition.

    The one exception to this rule is if the constructor for the inner object has no arguments (e.g. the Counter() class from chapter 2). In this case it will be called automatically without the programmer having to worry.

    It is useful to be aware that objects are constructed from the inside outwards - this means that the constructors for any inner objects are called before those for the outer object. This is illustrated by the following example program.

    Observe how the constructor for Jane has to call that for Jim in order to build the data member "a".

    The following output is generated when the program is compiled and run, showing the order in which the various constructor functions are called.

    3.5. The Cashpoint class

    The definition of the Cashpoint class is as follows.

    The interface functions to the cashpoint allow the user to log on to a specific account (checking their PIN in the process), to withdraw money, obtain a balance of account and then log off.

    Any of these could produce an error (e.g. trying to withdraw money before logging on to an account). The handling of these errors needs careful thought. A first reaction might be to print out error messages on the standard error channel from within the methods. This is a BAD idea because the Account class has no conception of what the user interface to the Cashpoint looks like (is it a dumb terminal or a PC with windows?). Instead, it is much better if the Account methods return a status code so that the program (or object) that called them can decide what to do. As a result, the Account class definition also defines the status codes that can be returned by its methods. These take the form of constant named integer values. This style of error handling is generally good practice.

    Notice that becuase its result is a status code, the balance method actually passes the balance value back through the reference parameter Money &amount. This is an example of "pass by reference". In this case, we are using pass by reference so that we can have the deliberate side-effect of passing a second value back out of the function. Because we want a side-effect we mustn't use the word const in the declaration of this paramter.

    The implementation of the Cashpoint class is included below.

    The implementation of the Cashpoint simulator accesses an array of Account objects declared as in internal data member.

    The constructor for Cashpoint has to initialise the objects in this array. Of course, in the real-world, the accounts would be set up by some other program, not by the cashpoint machine each time it was started up. Also notice the syntax for invoking a method on an object stored in this array:-

    This example says, "get the ith element of the accounts array (an object of class Account) and invoke the set_account_id method on it".

    3.6. The Interface Program

    So far we have seen a Money class, an Account class which uses the Money class and a Cashpoint class which uses the Account and Money classes. To complete the program we need a "user interface" which communicates with the user and invokes the appropriate methods on a Cashpoint object. This is where the function main() will be defined.

    The function main() sets up a Cashpoint object and then follows a continuous while loop, logging the user onto an account. When the user is logged on, the function do_transactions is called. This sits in a second continuous loop, presenting users with a menu of choices, reading their selection, processing it though a switch statement and invoking the appropriate methods on the cashpoint object. Logging out of the account returns to the outer while loop in the function main.

    Two other useful functions are defined: give_money prints out a combination of 50, 20, 10 and 5 pounds notes which make up the amount withdrawn. Print_error converts status codes from the Cashpoint into error messages.

    One feature of this design is that we could easily build alternative user interfaces to the same Cashpoint class.

    3.7. File relationships and compilation

    The overall system has been divided up into a number of modules representing different C++ classes. Provided that the header (.h) files are well defined, each implementation could be written independently by a different member of a programming team.

    The following commands would be needed to compile the various components into a final executable program.

    Compile the Money implementation:

  • g++ -c Money.C

    Compile the Account implementation:

  • g++ -c Account.C

    Compile the Cashpoint implementation:

  • g++ -c Cashpoint.C

    Compile the interface and link in all the other compiled code:

  • g++ Interface.C Cashpoint.o Account.o Money.o

    Note that the order of the .o files in the last command is significant!


    Notes converted from troff to HTML by an Eric Foxley shell script, email errors to me!