We now revisit our ATM system design to see how it might benefit from inheritance and polymorphism. To apply inheritance, we first look for commonality among classes in the system. We create an inheritance hierarchy to model similar classes in an elegant and efficient manner that enables us to process objects of these classes polymorphically. We then modify our class diagram to incorporate the new inheritance relationships. Finally, we demonstrate how the inheritance aspects of our updated design are translated into C# code.
In Section 4.11, we encountered the problem of representing a financial transaction in the system. Rather than create one class to represent all transaction types, we created three distinct transaction classesBalanceInquiry, Withdrawal and Depositto represent the transactions that the ATM system can perform. The class diagram of Fig. 11.19 shows the attributes and operations of these classes. Note that they have one private attribute (accountNumber) and one public operation (Execute) in common. Each class requires attribute accountNumber to specify the account to which the transaction applies. Each class contains operation Execute, which the ATM invokes to perform the transaction. Clearly, BalanceInquiry, Withdrawal and Deposit represent types of transactions. Figure 11.19 reveals commonality among the transaction classes, so using inheritance to factor out the common features seems appropriate for designing these classes. We place the common functionality in base class transaction and derive classes BalanceInquiry, Withdrawal and Deposit from transaction (Fig. 11.20).
Figure 11.19. Attributes and operations of classes BalanceInquiry, Withdrawal and Deposit.
Figure 11.20. Class diagram modeling the generalization (i.e., inheritance) relationship between the base class transaction and its derived classes BalanceInquiry, Withdrawal and Deposit.
The UML specifies a relationship called a generalization to model inheritance. Figure 11.20 is the class diagram that models the inheritance relationship between base class transaction and its three derived classes. The arrows with triangular hollow arrowheads indicate that classes BalanceInquiry, Withdrawal and Deposit are derived from class transaction by inheritance. Class transaction is said to be a generalization of its derived classes. The derived classes are said to be specializations of class TRansaction.
As Fig. 11.19 shows, classes BalanceInquiry, Withdrawal and Deposit share private int attribute accountNumber. We'd like to factor out this common attribute and place it in the base class TRansaction. However, recall that a base class's private attributes are not accessible in derived classes. The derived classes of TRansaction require access to attribute accountNumber so that they can specify which Account to process in the BankDatabase. As you learned in Chapter 10, a derived class can access only the public, protected and protected internal members of its base class. However, the derived classes in this case do not need to modify attribute accountNumberthey need only to access its value. For this reason, we have chosen to replace private attribute accountNumber in our model with the public read-only property AccountNumber. Since this is a read-only property, it provides only a get accessor to access the account number. Each derived class inherits this property, enabling the derived class to access its account number as needed to execute a transaction. We no longer list accountNumber in the second compartment of each derived class, because the three derived classes inherit property AccountNumber from transaction.
According to Fig. 11.19, classes BalanceInquiry, Withdrawal and Deposit also share operation Execute, so base class transaction should contain public operation Execute. However, it does not make sense to implement Execute in class transaction, because the functionality that this operation provides depends on the specific type of the actual transaction. We therefore declare Execute as an abstract operation in base class transactionit will become an abstract method in the C# implementation. This makes transaction an abstract class and forces any class derived from transaction that must be a concrete class (i.e., BalanceInquiry, Withdrawal and Deposit) to implement the operation Execute to make the derived class concrete. The UML requires that we place abstract class names and abstract operations in italics. Thus, in Fig. 11.20, transaction and Execute appear in italics for the transaction class; Execute is not italicized in derived classes BalanceInquiry, Withdrawal and Deposit. Each derived class overrides base class transaction's Execute operation with an appropriate concrete implementation. Note that Fig. 11.20 includes operation Execute in the third compartment of classes BalanceInquiry, Withdrawal and Deposit, because each class has a different concrete implementation of the overridden operation.
As you learned in this chapter, a derived class can inherit interface and implementation from a base class. Compared to a hierarchy designed for implementation inheritance, one designed for interface inheritance tends to have its functionality lower in the hierarchya base class signifies one or more operations that should be defined by each class in the hierarchy, but the individual derived classes provide their own implementations of the operation(s). The inheritance hierarchy designed for the ATM system takes advantage of this type of inheritance, which provides the ATM with an elegant way to execute all transactions "in the general" (i.e., polymorphically). Each class derived from transaction inherits some implementation details (e.g., property AccountNumber), but the primary benefit of incorporating inheritance into our system is that the derived classes share a common interface (e.g., abstract operation Execute). The ATM can aim a TRansaction reference at any transaction, and when the ATM invokes the operation Execute through this reference, the version of Execute specific to that transaction runs (polymorphically) automatically (due to polymorphism). For example, suppose a user chooses to perform a balance inquiry. The ATM aims a transaction reference at a new object of class BalanceInquiry, which the C# compiler allows because a BalanceInquiry is a transaction. When the ATM uses this reference to invoke Execute, BalanceInquiry's version of Execute is called (polymorphically).
This polymorphic approach also makes the system easily extensible. Should we wish to create a new transaction type (e.g., funds transfer or bill payment), we would simply create an additional TRansaction derived class that overrides the Execute operation with a version appropriate for the new transaction type. We would need to make only minimal changes to the system code to allow users to choose the new transaction type from the main menu and for the ATM to instantiate and execute objects of the new derived class. The ATM could execute transactions of the new type using the current code, because it executes all transactions identically (through polymorphism).
As you learned earlier in the chapter, an abstract class like transaction is one for which the programmer never intends to (and, in fact, cannot) instantiate objects. An abstract class simply declares common attributes and behaviors for its derived classes in an inheritance hierarchy. Class TRansaction defines the concept of what it means to be a transaction that has an account number and can be executed. You may wonder why we bother to include abstract operation Execute in class TRansaction if Execute lacks a concrete implementation. Conceptually, we include this operation because it is the defining behavior of all transactionsexecuting. Technically, we must include operation Execute in base class transaction so that the ATM (or any other class) can invoke each derived class's overridden version of this operation polymorphically via a transaction reference.
Derived classes BalanceInquiry, Withdrawal and Deposit inherit property AccountNumber from base class transaction, but classes Withdrawal and Deposit contain the additional attribute amount that distinguishes them from class BalanceInquiry. Classes Withdrawal and Deposit require this additional attribute to store the amount of money that the user wishes to withdraw or deposit. Class BalanceInquiry has no need for such an attribute and requires only an account number to execute. Even though two of the three TRansaction derived classes share the attribute amount, we do not place it in base class TRansactionwe place only features common to all the derived classes in the base class, so derived classes do not inherit unnecessary attributes (and operations).
Figure 11.21 presents an updated class diagram of our model that incorporates inheritance and introduces abstract base class transaction. We model an association between class ATM and class TRansaction to show that the ATM, at any given moment, either is executing a transaction or is not (i.e., zero or one objects of type transaction exist in the system at a time). Because a Withdrawal is a type of TRansaction, we no longer draw an association line directly between class ATM and class Withdrawalderived class Withdrawal inherits base class TRansaction's association with class ATM. Derived classes BalanceInquiry and Deposit also inherit this association, which replaces the previously omitted associations between classes BalanceInquiry and Deposit, and class ATM. Note again the use of triangular hollow arrowheads to indicate the specializations (i.e., derived classes) of class transaction, as indicated in Fig. 11.20.
Figure 11.21. Class diagram of the ATM system (incorporating inheritance). Note that abstract class name TRansaction appears in italics.
(This item is displayed on page 552 in the print version)
We also add an association between class transaction and the BankDatabase (Fig. 11.21). All TRansactions require a reference to the BankDatabase so that they can access and modify account information. Each TRansaction derived class inherits this reference, so we no longer model the association between class Withdrawal and the BankDatabase. Note that the association between class transaction and the BankDatabase replaces the previously omitted associations between classes BalanceInquiry and Deposit, and the BankDatabase.
We include an association between class transaction and the Screen because all transactions display output to the user via the Screen. Each derived class inherits this association. Therefore, we no longer include the association previously modeled between Withdrawal and the Screen. Class Withdrawal still participates in associations with the CashDispenser and the Keypad, howeverthese associations apply to derived class Withdrawal but not to derived classes BalanceInquiry and Deposit, so we do not move these associations to base class transaction.
Our class diagram incorporating inheritance (Fig. 11.21) also models classes Deposit and BalanceInquiry. We show associations between Deposit and both the DepositSlot and the Keypad. Note that class BalanceInquiry takes part in only those associations inherited from class transactiona BalanceInquiry interacts only with the BankDatabase and the Screen.
The class diagram of Fig. 9.23 showed attributes, properties and operations with visibility markers. Now we present a modified class diagram in Fig. 11.22 that includes abstract base class transaction. This abbreviated diagram does not show inheritance relationships (these appear in Fig. 11.21), but instead shows the attributes and operations after we have employed inheritance in our system. Note that abstract class name transaction and abstract operation name Execute in class transaction appear in italics. To save space, as we did in Fig. 5.19, we do not include those attributes shown by associations in Fig. 11.21we do, however, include them in the C# implementation in Appendix J. We also omit all operation parameters, as we did in Fig. 9.23incorporating inheritance does not affect the parameters already modeled in Figs. 7.227.25.
Figure 11.22. Class diagram after incorporating inheritance into the system.
Implementing the ATM System Design Incorporating Inheritance
In Section 9.17, we began implementing the ATM system design in C# code. We now modify our implementation to incorporate inheritance, using class Withdrawal as an example.
Figure 11.23. C# code for shell of class Withdrawal.
1 // Fig 11.23: Withdrawal.cs 2 // Class Withdrawal represents an ATM withdrawal transaction. 3 public class Withdrawal : Transaction 4 { 5 // code for members of class Withdrawal 6 } // end class Withdrawal |
Figure 11.24. C# code for class Withdrawal based on Fig. 11.21 and Fig. 11.22.
1 // Fig 11.24: Withdrawal.cs 2 // Class Withdrawal represents an ATM withdrawal transaction. 3 public class Withdrawal : Transaction 4 { 5 // attributes 6 private decimal amount; // amount to withdraw 7 private Keypad keypad; // reference to keypad 8 private CashDispenser cashDispenser; // reference to cash dispenser 9 10 // parameterless constructor 11 public Withdrawal() 12 { 13 // constructor body code 14 } // end constructor15 16 // method that overrides Execute 17 public override void Execute() 18 { 19 // Execute method body code 20 } // end method Execute 21 } // end class Withdrawal |
We discuss the polymorphic processing of TRansactions in Section J.2 of the ATM implementation. Class ATM performs the actual polymorphic call to method Execute at line 99 of Fig. J.1.
ATM Case Study Wrap-Up
This concludes our object-oriented design of the ATM system. A complete C# implementation of the ATM system in 655 lines of code appears in Appendix J. This working implementation uses most of the key object-oriented programming concepts that we have covered to this point in the book, including classes, objects, encapsulation, visibility, composition, inheritance and polymorphism. The code is abundantly commented and conforms to the coding practices you've learned so far. Mastering this code is a wonderful capstone experience for you after studying the nine Software Engineering Case Study sections in Chapters 1, 39 and 11.
Software Engineering Case Study Self-Review Exercises
11.1 |
The UML uses an arrow with a __________ to indicate a generalization relationship.
|
11.2 |
State whether the following statement is true or false, and if false, explain why: The UML requires that we underline abstract class names and abstract operation names. |
11.3 |
Write C# code to begin implementing the design for class transaction specified in Fig. 11.21 and Fig. 11.22. Be sure to include private references based on class TRansaction's associations. Also, be sure to include properties with public get accessors for any of the private instance variables that the derived classes must access to perform their tasks. |
Answers to Software Engineering Case Study Self-Review Exercises
11.1 |
b. |
11.2 |
False. The UML requires that we italicize abstract class names and operation names. |
11.3 |
The design for class transaction yields the code in Fig. 11.25. In the implementation in Appendix J, a constructor initializes private instance variables userScreen and database to actual objects, and read-only properties UserScreen and Database access these instance variables. These properties allow classes derived from transaction to access the ATM's screen and interact with the bank's database. Note that we chose the names of the UserScreen and Database properties for claritywe wanted to avoid property names that are the same as the class names Screen and BankDatabase, which can be confusing. |
Figure 11.25. C# code for class TRansaction based on Fig. 11.21 and Fig. 11.22.
1 // Fig 11.25: Transaction.cs 2 // Abstract base class Transaction represents an ATM transaction. 3 public abstract class Transaction 4 { 5 private int accountNumber; // indicates account involved 6 private Screen userScreen; // ATM's screen 7 private BankDatabase database; // account info database 8 9 // parameterless constructor 10 public Transaction() 11 { 12 // constructor body code 13 } // end constructor 14 15 // read-only property that gets the account number 16 public int AccountNumber 17 { 18 get 19 { 20 return accountNumber; 21 } // end get 22 } // end property AccountNumber 23 24 // read-only property that gets the screen reference 25 public Screen UserScreen 26 { 27 get 28 { 29 return userScreen; 30 } // end get 31 } // end property UserScreen 32 33 // read-only property that gets the bank database reference 34 public BankDatabase Database 35 { 36 get 37 { 38 return database; 39 } // end get 40 } // end property Database 41 42 // perform the transaction (overridden by each derived class) 43 public abstract void Execute(); 44 } // end class Transaction |
Preface
Index
Introduction to Computers, the Internet and Visual C#
Introduction to the Visual C# 2005 Express Edition IDE
Introduction to C# Applications
Introduction to Classes and Objects
Control Statements: Part 1
Control Statements: Part 2
Methods: A Deeper Look
Arrays
Classes and Objects: A Deeper Look
Object-Oriented Programming: Inheritance
Polymorphism, Interfaces & Operator Overloading
Exception Handling
Graphical User Interface Concepts: Part 1
Graphical User Interface Concepts: Part 2
Multithreading
Strings, Characters and Regular Expressions
Graphics and Multimedia
Files and Streams
Extensible Markup Language (XML)
Database, SQL and ADO.NET
ASP.NET 2.0, Web Forms and Web Controls
Web Services
Networking: Streams-Based Sockets and Datagrams
Searching and Sorting
Data Structures
Generics
Collections
Appendix A. Operator Precedence Chart
Appendix B. Number Systems
Appendix C. Using the Visual Studio 2005 Debugger
Appendix D. ASCII Character Set
Appendix E. Unicode®
Appendix F. Introduction to XHTML: Part 1
Appendix G. Introduction to XHTML: Part 2
Appendix H. HTML/XHTML Special Characters
Appendix I. HTML/XHTML Colors
Appendix J. ATM Case Study Code
Appendix K. UML 2: Additional Diagram Types
Appendix L. Simple Types
Index