Part 11: Polymorphism

 
 

11.1 Specification

        "There are three types of bank account: savings, cheque, and investment. A customer may have one account of each type. Savings and cheque accounts are accessed through the ATM. Savings and investment accounts accrue daily interest.

        A successful withdrawal from a cheque account costs 50 cents. An unsuccessful withdrawal from a cheque account (a bounced cheque) costs $5. There are no charges or penalties for a savings account. A savings account gets daily interest; the interest rate is 4.5% a year. A cheque account gets no interest. The balance of an account cannot be negative.

        An investment account may not be accessed through the ATM. It is created with an initial balance, and accrues daily interest for a period of 3, 6, or 12 months. A 3-month investment account has an annual interest rate of 5.5%, a 6-month account has a 6.0% rate, and a 12-month account 6.5%. When the account matures at the end of the period, the total amount is transferred into the customer's cheque account."
 
 

11.2 Analysis

        The focus of the analysis is the inheritance hierarchy for the types of account, and this part of the system is discussed in detail below. In addition to this, system is extended to create, store, find, and use three accounts. The menu system is extended to ask the customer to select one of the accounts. The class TELLER is extended to create new accounts for an existing customer; this is an implicit goal for the new system.

        All accounts have a balance, and an interest rate, although the exact rate differs for each account and for each period of an investment account. All accounts are created with an initial balance, but an investment account also requires the period. Savings and cheque accounts can receive deposits. Money can also be withdrawn from each type of account, but the rules are slightly different, both for how much is enough and for the cost of a transaction. Both savings and cheque accounts only display the balance, but a separate routine to display the period must be written for the three investment accounts. Interest is added to all accounts in the same way, daily. Finally, the investment account has to keep some kind of counter to check if the account has matured; this involves an attribute, and a test of the attribute value.
  
SAVING 
CHEQUE
INVEST
        balance
x
x
x
        rate
o
o
o
        make
x
x
o
        deposit
x
x
        withdraw
o
o
        show balance
x
x
        display
o
o
o
        left
x
        mature?
x
        interest
x
x
x

         The table from this analysis is shown above, where a cross indicates the same content across classes, and a circle indicates a different content. The table provides a basis for the design of the account inheritance hierarchy.
 
 

11.3 Types of account

        The class ACCOUNT looks as though it contains a balance, a rate, and an interest routine. The class INVEST inherits from ACCOUNT, and adds features to store the period, to count each day and to check if the account is mature. The other two types of account support deposit and withdraw, so an abstract class INTERACCT (interactive account) can be defined. Two classes SAVINGS and CHEQUE are used to deal with the different rules for withdrawing money. The inheritance hierarchy is shown below, with a deferred feature indicated by a star.

        A choice has to be made about whether to use polymorphism here or not. The accounts share a lot of behaviour, a good reason to use polymorphism. The accounts also do not share a lot of behaviour, a good reason not to use polymorphism. If a polymorphic list of accounts is used, then all accounts have the same exported features, and these features must be supplied in class ACCOUNT even if the child features do nothing; we have routines that does nothing, and exist only because some other class needs the feature. Because the interfaces to the accounts are so different, the choice has been made to not use a polymorphic list of accounts. This creates a very clean inheritance structure.

        The price of not using polymorphism is complex code in CUSTOMER. A customer has three accounts, and an account has to be found and used given an account key. A list scan cannot be used, because there is no list of accounts. The solution is to define a class ACCOUNTS that behaves like a list: it supports find and found.
 

11.3.1         Focus: account balance

        All accounts have a balance, so this attribute is stored in class ACCOUNT, together with the routines that set and display the balance. An outline of class ACCOUNT is given below; note the empty creation clause.

class ACCOUNT

creation

feature {NONE}
        balance: REAL

        set_balance is
                        -- read the balance and store it

        show_balance is
                        -- show the balance

feature {CUSTOMER}

        make is
                        -- set the initial account balance
               do set_balance end -- make

        show is
                        -- show the account balance
               do show_balance end -- make

end -- class ACCOUNT
 

11.3.2         Focus: account id

        There are now three types of account in the system: savings, cheque, and investment accounts. These will be stored on a list, so we need a mechanism to search the list and find the desired type of account. The simplest solution is to store a flag with each type of account (say ‘S’, ‘C’, and ‘I’) and match on this flag. This is not an avoidance of polymorphism, because we simply wish to retrieve the object, not to process the objects differently.

        An attribute id is thus addedto ACCOUNT, along with the routines to set, display, and match this attribute. The actual value stored in the id cannot be stated at the ACCOUNT level, so the feature is deferred to the children and the class is a deferred class. An outline of the added and changed ACCOUNT features is shown below:

deferred class ACCOUNT

feature {NONE}
        id: CHARACTER

        set_id is
                        -- set the account id
               deferred end -- set_id

        show_id is
                        -- show the account identifier
               do
                       io.putstring ("%N%TThe account id is ")
                       io.putchar (id)
               end -- show_id

feature {CUSTOMER}
        make is
                        -- set the account id and initial account balance
               do
                       set_id
                       set_balance
               end -- make

        show is
                        -- show the account id and balance
               do
                       show_id
                       show_balance
               end -- make

        match (key: CHARACTER): BOOLEAN is
                        -- does this key match the account id?
               do
                       Result := key = id
               end -- match

end -- class ACCOUNT
 

11.3.3         Focus: interest rate

        Not all accounts receive interest. There are two ways to deal with this problem. The obvious solution is to have an attribute rate in ACCOUNT, that is set to zero in the class CHEQUE and to the actual interest rate in the other classes. The CUSTOMER then uses polymorphism and calls an add_interest routine for all accounts; a cheque account adds 0.0 as interest. The problem is that not all accounts get interest, so the rate in ACCOUNT is misleading.

        A second solution is to to not use polymorphism, and filter out the cheque accounts when interest is added. Because a key is used to find or check the type of account, this is no problem. This approach allows us to define a class INTEREST that contains the rate and the routines to set and show the rate. Nothing can be said about the value of the rate at this level, so the routine to set the rate is a deferred feature. An outline of this class is shown below:

deferred class INTEREST

inherit
        ACCOUNT
               redefine make
               end

feature {NONE}
        rate: REAL

        set_rate is
                        -- set the interest rate
               deferred end -- set_rate

        show_rate is
                        -- show the interest rate

feature {CUSTOMER}
        make is
                        -- set the id, balance and the rate
               do
                       set_id
                       set_balance
                       set_rate
               end -- make

        add_interest is
                        -- add the daily interest to the balance

end -- class INTEREST
 

11.3.4         Focus: an interactive account

        Not all accounts can be accessed via an ATM, only savings and cheque accounts. A class can be defined to capture the features unique to an interactive account: deposit and withdraw. Before a customer can withdraw money, the sysetm must check if there is enough money in the account. An outline of class INTERACCT (interactive account) is shown below:

deferred class INTERACCT

inherit
        ACCOUNT
        MENU

feature {CUSTOMER}
        deposit (amount: REAL) is
                        -- add amount to balance

        enough (amount: REAL): BOOLEAN is
                        -- does the account contain this amount?

        withdraw (amount: REAL) is
                        -- subtract this amount from the balance

end -- class INTERACCT

The MENU controls the user interaction, and calls the features deposit, enough, and withdraw.
 

11.3.5         Focus: withdraw

        The class INTERACCT provides a basic withdraw routine that cannot fail. It is possible to fail to withdraw from SAVINGS and CHEQUE accounts, if the account does not contain enough money. The withdrawal rules are different for a savings account and a cheque account, as shown below, but both child features use the parent features enough and withdraw.
  
                SAVINGS  CHEQUE
                if enough (money) if enough (money + charge)
                then withdraw (money) then withdraw (money + charge)
else penalise

         One solution is to define two routines called withdraw in SAVINGS and in CHEQUE. The withdraw routine in ACCOUNT is named Precursor in the child classes. An outline of the code in these classes is shown below, with their effective id and rate features. This solution uses several language constructs (undefine, select) that are not introduced until the next chapter; they are used here to define a simple inheritance structure.

class SAVINGS

inherit
        INTERACCT
               rename withdraw as Precursor
               undefine make
               end
        INTERACCT
               undefine make
               redefine withdraw
               select withdraw
               end
        INTEREST
        ...

feature {CUSTOMER}
        withdraw (amount: REAL) is
                        -- withdraw this amount if there is enough money
               do
                       if enough (amount)
                       then Precursor (amount)
                       else io.putstring ("%NInsufficient funds")
                       end
               end -- try_withdraw

end -- class SAVINGS
 

class CHEQUE

inherit
        INTERACCT
               rename withdraw as Precursor
               end
        INTERACCT
               redefine withdraw
               select withdraw
               end
        ...

feature {NONE}
        charge: REAL is 0.50                          -- charge for good transaction
        penalty: REAL is 5.00                  -- penalty for bouncing a check
        ...

feature {CUSTOMER}
        withdraw (amount: REAL) is
                        -- if there is enough money, withdraw the amount
                        -- if not, charge a penalty for bouncing a check
               do
                       if enough (amount + charge)
                       then Precursor (amount + charge)
                       else penalise
                       end
               end -- try_withdraw

        penalise is
                        -- apply the penalty for bouncing a check (balance cannot go negative)
               do
                       if enough (penalty)
                       then balance := balance - penalty
                       else balance := 0
                       end
               end -- apply_penalty

end -- class CHEQUE
 
 

11.4 Storing the accounts

        Because I decided to not use polymorphism, nothing is gained by storing the three accounts on a list of accounts and much is lost, because all the interface features would need to be defined in ACCOUNT for the system to compile. Instead, three attributes are used:
  
                savings: SAVINGS
                cheque: CHEQUE
                invest: INVEST

         A decision must be made about where these attributes, and the code that sets and uses them, are placed. There is a fair amount of code involved in getting a key from the user, selecting the account and then using it. Placing this code in CUSTOMER creates a very large class, most of which is actually about the accounts. For this reason, a class ACCOUNTS has been defined that contains the three accounts and their code.
 
 

11.5 Inheritance chart

        Class ACCOUNT defines the features common to all accounts, INTEREST defines effective features to add interest, andINTERACCT defines the features for interactive accounts. The bottom classes define the values and behaviour for investment, savings, and cheque accounts.

        The class INTERACCT is shown here, and in the following listing, inheriting from MENU. This is done by multiple inheritance, a topic presented in the next chapter. This is done to make the account inheritance structure simple and to localise the ATM interaction to the class INTERACCT. A class listing for the new MENU is deferred until the next part of the case study.


 
 

11.6 Client chart

        The client chart is shown below in two parts. There are two changes from the previous case. First, a customer uses a class ACCOUNTS that contains one account of each type. Second, the MENU is inherited by an account, and is no longer a client of CUSTOMER..


 
 

11.7 Class diagrams

        Class diagrams for the classes CUSTOMER, ACCOUNTS, and the classes in the account hierarchy are shown below.


 
 

11.8 Solution code

        A partial listing is given for class TELLER, to show its calls to features in CUSTOMER; ATM operations are similar, and so are not shown. A full listing is given for class CUSTOMER, except for the code to set and check the customer’s id shown in the previous section. A full listing is then given for class ACCOUNTS. The classes in the account hierarchy are then listed, except for MENU; a MENU listing is given in the next part of the case study.
 

class TELLER

creation {BANK}
        make

feature {NONE}
        patrons: LINKED_LIST [CUSTOMER]

        make (customers: LINKED_LIST [CUSTOMER]) is
                        -- set the patrons to the list of customers
               do patrons := customers end -- make

feature {BANK}
        run is
                        -- create new customers, create new accounts for existing customers
               do
                       show_header
                       new_customers
                       new_accounts
               end -- run

feature {NONE}
        show_header is
                        -- show the teller a nice message
               do
                       io.putstring ("%N")
                       io.putstring ("%N**************************************")
                       io.putstring ("%N* Add new customers and new accounts *")
                       io.putstring ("%N**************************************")
               end

        new_customers is
                        -- add any new customers with their initial accounts
               local patron: CUSTOMER
               do
                       from ask_for_more_customers
                       until no_more
                       loop
                               !!patron.make (patrons.count + 1)
                               patrons.extend (patron)
                               ask_for_more_customers
                       end
               end -- new_customers

        ask_for_more_customers is
                        -- prompt the user for more customers, read reply
               do
                       io.putstring ("%NAny customers to add (Y/N)? ")
                       io.readchar
                       io.next_line
               end -- ask_for_more_customers

        no_more: BOOLEAN is
                        -- did the user type in the no code?
               do
                       Result := io.lastchar.upper = 'N'
               end -- no_chosen

        new_accounts is
                        -- add any new accounts for existing customers
               do
                       from ask_for_more_accounts
                       until no_more
                       loop
                               read_id
                               find (io.lastint)
                               if found
                               then patrons.item.accounts.make
                               else io.putstring ("%NThat is not a valid userId")
                               end
                               ask_for_more_accounts
                       end
               end -- new_accounts

        ask_for_more_accounts is
  &n