"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."
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.
|
|
|
|
balance |
|
|
|
rate |
|
|
|
make |
|
|
|
deposit |
|
|
|
withdraw |
|
|
|
show balance |
|
|
|
display |
|
|
|
left |
|
||
mature? |
|
||
interest |
|
|
|
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.
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
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.
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.
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..
Class diagrams for the classes CUSTOMER, ACCOUNTS, and the classes in the account hierarchy are shown below.
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