9.11. (Optional) Software Engineering Case
Study: Starting to Program the Classes of the ATM System
In the Software Engineering Case Study sections in Chapters
1–7, we introduced the fundamentals of object
orientation and developed an object-oriented design for our ATM system. Earlier
in this chapter, we discussed many of the details of programming with C++
classes. We now begin implementing our object-oriented design in C++. At the end
of this section, we show how to convert class diagrams to C++ header files. In
the final Software Engineering Case Study section (Section
13.10), we modify the header files to incorporate
the object-oriented concept of inheritance. We present the full C++ code
implementation in Appendix
E, ATM Case Study Code.
Visibility
We now apply access specifiers
to the members of our classes. In Chapter
3, we introduced access specifiers public and private.
Access specifiers determine the visibility or accessibility
of an object's attributes and operations to other objects. Before we can begin
implementing our design, we must consider which attributes and operations of our
classes should be public and which should be private.
In Chapter
3, we observed that data members normally should be
private and that member functions invoked by
clients of a given class should be public. Member
functions that are called only by other member functions of the class as
"utility functions," however, normally should be private. The UML employs visibility
markers for modeling the visibility of
attributes and operations. Public visibility is indicated by placing a plus sign
(+) before an operation or an attribute; a minus sign (–) indicates private
visibility. Figure
9.20 shows our updated class diagram with
visibility markers included. [Note: We do not include any operation parameters in Fig. 9.20. This is perfectly normal. Adding visibility
markers does not affect the parameters already modeled in the class diagrams of
Figs.
6.34–6.37.]
Navigability
Before we begin implementing our
design in C++, we introduce an additional UML notation. The class diagram in Fig. 9.21 further refines the relationships among classes in the ATM
system by adding navigability arrows to the association lines. Navigability arrows
(represented as arrows with stick arrowheads in the class diagram) indicate in
which direction an association can be traversed and are based on the
collaborations modeled in communication and sequence diagrams (see Section
7.12). When implementing a system designed using the
UML, programmers use navigability arrows to help determine which objects need
references or pointers to other objects. For example, the navigability arrow
pointing from class ATM to class
BankDatabase indicates that we can navigate
from the former to the latter, thereby enabling the ATM to invoke the
BankDatabase's operations. However, since Fig. 9.21 does not contain a navigability arrow pointing from
class BankDatabase to class ATM, the BankDatabase cannot access the ATM's operations.
Note that associations in a class diagram that have navigability arrows at both
ends or do not have navigability arrows at all indicate bidirectional navigability—navigation can proceed
in either direction across the association.
Like the class diagram of Fig.
3.23, the class diagram of Fig. 9.21 omits classes
BalanceInquiry and Deposit to keep
the diagram simple. The navigability of the associations in which these classes
participate closely parallels the navigability of class Withdrawal's associations.
Recall from Section
3.11 that BalanceInquiry has an association with class
Screen. We can navigate from class BalanceInquiry to class
Screen along this association, but we cannot navigate
from class Screen to class BalanceInquiry. Thus, if we were to model class BalanceInquiry
in Fig. 9.21, we
would place a navigability arrow at class Screen's end of this
association. Also recall that class Deposit associates with classes
Screen, Keypad and DepositSlot. We can navigate from class
Deposit to each of these classes, but
not vice versa. We therefore would place navigability arrows at the Screen,
Keypad and DepositSlot ends of these associations.
[Note: We model
these additional classes and associations in our final class diagram in Section
13.10, after we have simplified the structure
of our system by incorporating the object-oriented concept of
inheritance.]
Implementing the ATM System from
Its UML Design
We are now ready to begin implementing
the ATM system. We first convert the classes in the diagrams of Fig. 9.20 and Fig. 9.21
into C++ header files. This code will represent the "skeleton" of the system. In
Chapter
13, we modify the header files to incorporate the object-oriented concept of inheritance. In Appendix
E, 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. 9.20. We use this figure to determine the attributes and
operations of the class. We use the UML model in Fig. 9.21
to determine the associations among classes. We follow the following five
guidelines for each class:
Use the name located in the first
compartment of a class in a class diagram to define the class in a header file
(Fig. 9.22). Use #ifndef, #define and #endif preprocessor directives to prevent the header file from
being included more than once in a program.
Fig. 9.22. Definition of class Withdrawal
enclosed in preprocessor wrappers.
1 // Fig. 9.22: 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
|
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. 9.23.
Fig. 9.23. Adding attributes to the
Withdrawal class header file.
1 // Fig. 9.23: 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
|
Use the associations described in the
class diagram to declare references (or pointers, where appropriate) to other
objects. For example, according to Fig. 9.21, 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. 9.24 declare
four references as private data members. In the
implementation of class Withdrawal in Appendix
E, a constructor initializes these data members with
references to actual objects. Note that 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.
Fig. 9.24. Declaring
references to objects associated with class Withdrawal.
1 // Fig. 9.24: 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
|
It turns out that including the header files for classes
Screen, Keypad, CashDispenser and BankDatabase in Fig. 9.24 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 #includes in the Withdrawal class
definition of Fig.
9.24 with forward declarations of classes Screen, Keypad,
CashDispenser and BankDatabase (lines 6–9 in Fig. 9.25). 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. Note that if class
Withdrawal contained actual objects instead of
references (i.e., if the ampersands in lines 19–22 were omitted), then we would
indeed need to #include the full header files.
Fig. 9.25. Using forward
declarations in place of #include directives.
1 // Fig. 9.25: 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
|
Note that 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 #includes 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.
Use the operations located in the third compartment of Fig. 9.20 to write the function prototypes of the class's member
functions. If we have 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.21–6.24 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. 9.26. [Note: We code the
definitions of member functions in .cpp
files when we implement the complete ATM system in Appendix
E.]
Fig. 9.26. Adding operations to the Withdrawal
class header file.
1 // Fig. 9.26: 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 9.11
|
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 the Internet and web resources listed at the end of Section 2.7. |
This concludes our discussion of
the basics of generating class header files from UML diagrams. In Section
13.10, we demonstrate how to modify the header
files to incorporate the object-oriented concept of inheritance.
Software Engineering Case Study
Self-Review Exercises
9.1 |
State whether the following statement is true or false, and if false, explain why: If an attribute of a class is marked with a minus sign (-) in a class diagram, the attribute is not directly accessible outside of the class. |
9.2 |
In Fig. 9.21, the association between the ATM and the Screen indicates that:
we can navigate from the Screen to the ATM
we can navigate from the ATM to the Screen
Both a and b; the association is bidirectional
|
9.3 |
Write C++ code to begin implementing the design for class Account. |
Answers to Software Engineering
Case Study Self-Review Exercises
9.1 |
True. The minus sign (–) indicates private visibility. We've mentioned "friendship" as an exception to private visibility. Friendship is discussed in Chapter 10. |
9.2 |
b. |
9.3 |
The design for class Account yields the header file in Fig. 9.27.
Fig. 9.27. Account class header file based on Fig. 9.20 and Fig. 9.21.
1 // Fig. 9.27: Account.h 2 // Account class definition. Represents a bank account. 3 #ifndef ACCOUNT_H 4 #define ACCOUNT_H 5 6 class Account 7 { 8 public: 9 bool validatePIN( int ); // is user-specified PIN correct? 10 double getAvailableBalance(); // returns available balance 11 double getTotalBalance(); // returns total balance 12 void credit( double ); // adds an amount to the Account 13 void debit( double ); // subtracts an amount from the Account 14 private: 15 int accountNumber; // account number 16 int pin; // PIN for authentication 17 double availableBalance; // funds available for withdrawal 18 double totalBalance; // funds available + funds waiting to clear 19 }; // end class Account 20 21 #endif // ACCOUNT_H
|
|
No comments:
Post a Comment