We are now ready to begin implementing the ATM system. We first convert the classes in the diagrams of Fig. 23.1 and Fig. 23.2 into C++ header files. This code will represent the “skeleton” of the system. In Section 23.3, we modify the header files to incorporate the object-oriented concept of inheritance. In Section 23.4, we present the complete working C++ code for our model.
As an example, we begin to develop the header file for class Withdrawal
from our design of class Withdrawal
in Fig. 23.1. We use this figure to determine the attributes and operations of the class. We use the UML model in Fig. 23.2 to determine the associations among classes. We follow the following five guidelines for each class:
1. Use the name in the first compartment of a class in a class diagram to define the class in a header file (Fig. 23.3). Use #ifndef
, #define
and #endif
preprocessor directives to prevent the header from being included more than once in a program.
1 // Fig. 23.3: Withdrawal.h
2 // Definition of class Withdrawal that represents a withdrawal transaction
3 #ifndef WITHDRAWAL_H
4 #define WITHDRAWAL_H
5
6 class Withdrawal
7 {
8 }; // end class Withdrawal
9
10 #endif // WITHDRAWAL_H
2. Use the attributes located in the class’s second compartment to declare the data members. For example, the private
attributes accountNumber
and amount
of class Withdrawal
yield the code in Fig. 23.4.
1 // Fig. 23.4: Withdrawal.h
2 // Definition of class Withdrawal that represents a withdrawal transaction
3 #ifndef WITHDRAWAL_H
4 #define WITHDRAWAL_H
5
6 class Withdrawal
7 {
8 private:
9 // attributes
10 int accountNumber; // account to withdraw funds from
11 double amount; // amount to withdraw
12 }; // end class Withdrawal
13
14 #endif // WITHDRAWAL_H
3. Use the associations described in the class diagram to declare references (or pointers, where appropriate) to other objects. For example, according to Fig. 23.2, Withdrawal
can access one object of class Screen
, one object of class Keypad
, one object of class CashDispenser
and one object of class BankDatabase
. Class Withdrawal
must maintain handles on these objects to send messages to them, so lines 19–22 of Fig. 23.5 declare four references as private
data members. In the implementation of class Withdrawal
in Section 23.4, a constructor initializes these data members with references to actual objects. Lines 6–9 #include
the header files containing the definitions of classes Screen
, Keypad
, CashDispenser
and BankDatabase
so that we can declare references to objects of these classes in lines 19–22.
1 // Fig. 23.5: Withdrawal.h
2 // Definition of class Withdrawal that represents a withdrawal transaction
3 #ifndef WITHDRAWAL_H
4 #define WITHDRAWAL_H
5
6 #include "Screen.h" // include definition of class Screen
7 #include "Keypad.h" // include definition of class Keypad
8 #include "CashDispenser.h" // include definition of class CashDispenser
9 #include "BankDatabase.h" // include definition of class BankDatabase
10
11 class Withdrawal
12 {
13 private:
14 // attributes
15 int accountNumber; // account to withdraw funds from
16 double amount; // amount to withdraw
17
18 // references to associated objects
19 Screen &screen; // reference to ATM's screen
20 Keypad &keypad; // reference to ATM's keypad
21 CashDispenser &cashDispenser; // reference to ATM's cash dispenser
22 BankDatabase &bankDatabase; // reference to the account info database
23 }; // end class Withdrawal
24
25 #endif // WITHDRAWAL_H
4. It turns out that including the header files for classes Screen
, Keypad
, CashDispenser
and BankDatabase
in Fig. 23.5 does more than is necessary. Class Withdrawal
contains references to objects of these classes—it does not contain actual objects—and the amount of information required by the compiler to create a reference differs from that which is required to create an object. Recall that creating an object requires that you provide the compiler with a definition of the class that introduces the name of the class as a new user-defined type and indicates the data members that determine how much memory is required to store the object. Declaring a reference (or pointer) to an object, however, requires only that the compiler knows that the object’s class exists—it does not need to know the size of the object. Any reference (or pointer), regardless of the class of the object to which it refers, contains only the memory address of the actual object. The amount of memory required to store an address is a physical characteristic of the computer’s hardware. The compiler thus knows the size of any reference (or pointer). As a result, including a class’s full header file when declaring only a reference to an object of that class is unnecessary—we need to introduce the name of the class, but we do not need to provide the data layout of the object, because the compiler already knows the size of all references. C++ provides a statement called a forward declaration that signifies that a header file contains references or pointers to a class, but that the class definition lies outside the header file. We can replace the #include
s in the Withdrawal
class definition of Fig. 23.5 with forward declarations of classes Screen
, Keypad
, CashDispenser
and BankDatabase
(lines 6–9 in Fig. 23.6). Rather than #include
the entire header file for each of these classes, we place only a forward declaration of each class in the header file for class Withdrawal
. If class Withdrawal
contained actual objects instead of references (i.e., if the ampersands in lines 19–22 were omitted), then we’d need to #include
the full header files.
1 // Fig. 23.6: Withdrawal.h
2 // Definition of class Withdrawal that represents a withdrawal transaction
3 #ifndef WITHDRAWAL_H
4 #define WITHDRAWAL_H
5
6 class Screen; // forward declaration of class Screen
7 class Keypad; // forward declaration of class Keypad
8 class CashDispenser; // forward declaration of class CashDispenser
9 class BankDatabase; // forward declaration of class BankDatabase
10
11 class Withdrawal
12 {
13 private:
14 // attributes
15 int accountNumber; // account to withdraw funds from
16 double amount; // amount to withdraw
17
18 // references to associated objects
19 Screen &screen; // reference to ATM's screen
20 Keypad &keypad; // reference to ATM's keypad
21 CashDispenser &cashDispenser; // reference to ATM's cash dispenser
22 BankDatabase &bankDatabase; // reference to the account info database
23 }; // end class Withdrawal
24
25 #endif // WITHDRAWAL_H
Using a forward declaration (where possible) instead of including a full header file helps avoid a preprocessor problem called a circular include. This problem occurs when the header file for a class A #include
s the header file for a class B
and vice versa. Some preprocessors are not be able to resolve such #include
directives, causing a compilation error. If class A
, for example, uses only a reference to an object of class B
, then the #include
in class A
’s header file can be replaced by a forward declaration of class B
to prevent the circular include.
5. Use the operations located in the third compartment of Fig. 23.1 to write the function prototypes of the class’s member functions. If we’ve not yet specified a return type for an operation, we declare the member function with return type void
. Refer to the class diagrams of Figs. 6.20–6.23 to declare any necessary parameters. For example, adding the public
operation execute
in class Withdrawal
, which has an empty parameter list, yields the prototype in line 15 of Fig. 23.7. [Note: We code the definitions of member functions in .cpp
files when we implement the complete ATM system in Section 23.4.]
1 // Fig. 23.7: Withdrawal.h
2 // Definition of class Withdrawal that represents a withdrawal transaction
3 #ifndef WITHDRAWAL_H
4 #define WITHDRAWAL_H
5
6 class Screen; // forward declaration of class Screen
7 class Keypad; // forward declaration of class Keypad
8 class CashDispenser; // forward declaration of class CashDispenser
9 class BankDatabase; // forward declaration of class BankDatabase
10
11 class Withdrawal
12 {
13 public:
14 // operations
15 void execute(); // perform the transaction
16 private:
17 // attributes
18 int accountNumber; // account to withdraw funds from
19 double amount; // amount to withdraw
20
21 // references to associated objects
22 Screen &screen; // reference to ATM's screen
23 Keypad &keypad; // reference to ATM's keypad
24 CashDispenser &cashDispenser; // reference to ATM's cash dispenser
25 BankDatabase &bankDatabase; // reference to the account info database
26 }; // end class Withdrawal
27
28 #endif // WITHDRAWAL_H
Software Engineering Observation 23.1
Several UML modeling tools can convert UML-based designs into C++ code, considerably speeding the implementation process. For more information on these “automatic” code generators, refer to our UML Resource Center at www.deitel.com/UML/.
This concludes our discussion of the basics of generating class header files from UML diagrams. In Section 23.3, we demonstrate how to modify the header files to incorporate the object-oriented concept of inheritance.