Tuesday, November 3, 2009

9.11. (Optional) Software Engineering Case Study: Starting to Program the Classes of the ATM System


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.]




Fig. 9.20. Class diagram with visibility markers.




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.




Fig. 9.21. Class diagram with navigability arrows.






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:






  1. 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





  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. 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





  3. 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





  4. 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.




  5. 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.1State 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.2In Fig. 9.21, the
association between the ATM and the Screen indicates that:




  1. we can navigate from the Screen
    to the ATM




  2. we can navigate from the
    ATM to the Screen




  3. Both a and b; the association is
    bidirectional




  4. None of the above

9.3Write C++ code to begin implementing the design for class
Account.


Answers to Software Engineering
Case Study Self-Review Exercises













9.1True. The minus sign ()
indicates private visibility. We've mentioned "friendship" as an exception to
private visibility. Friendship is discussed in Chapter
10.
9.2b.
9.3The 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