Sie sind auf Seite 1von 142

Case study

This section of the text shows the progressive development of a banking system, from one very
small class to the final solution of 16 classes that use almost every part of the Eiffel language. Each chapter
of the text (except the last two) has a corresponding case study section, to show how the language
constructs in that chapter are used in a working system. Each solution builds on the previous solution, and
shows the new or chnaged code in the extended solution. The final part of the case study presents the full
problem specification and solution.

The sections of the case study are

Part 1: Look and feel.


Part 2: Data flow.
Part 3: Routines
Part 4: Objects
Part 5: Behaviour
Part 6: Selection.
Part 7: Repetition.
Part 8: Arrays.
Part 9: Lists.
Part 10: Inheritance.
Part 11: Polymorphism.
Part 12: Complex inheritance.
Part 13: Constrained genericity.
Part 14: The complete BANK system.

Each section of the case study has the following format:


1. Specification.
2. Analysis.
3. Design.
4. Charts: one or more of client, inheritance, and class diagrams.
5. Eiffel code that is new or changed in that section.

Each section of the case study has a directory on the SoCS computer system, so the full, working
code for each section can be examined and executed. The text and executable files can be seen and run in
the directory
/pub/psda/oopie

Each directory contains the following files:


1. Specification.
2. Ace.
3. Eiffel text files.
4. Eiffel executable file, named bank.
5. A sample run of the system.

1
CASE STUDY 1

PART 1: LOOK AND FEEL 6


1.1 Specification 6

1.2 Analysis 6

1.3 Solution design 6

1.4 Client chart 7

1.5 Ace file 7

1.6 Solution code 8

PART 2: DATA FLOW 9


2.1 Specification 9

2.2 Analysis 9

2.3 Solution design 9

2.4 Solution code 9

PART 3: ROUTINES 11
3.1 Specification 11

3.2 Analysis 11

3.3 Solution design 11

3.4 Solution code 11

3.5 Common error 14

PART 4: OBJECTS 15
4.1 Specification 15

4.2 Analysis 15

4.3 Design 15

4.4 Client chart 15

4.5 Solution code 16

2
4.6 Common errors 22

PART 5: BEHAVIOUR 23
5.1 Specification 23

5.2 Analysis 23
5.2.1 Creation status 23
5.2.2 Export policies 23
5.2.3 Assertions 23

5.3 Design 24

5.4 Client chart and class diagrams 24

5.5 Solution code 24

5.6 Common errors 29

PART 6: SELECTION 30
6.1 Specification 30

6.2 Analysis 30

6.3 Design 30

6.4 Solution code 32

6.5 Common errors 35

PART 7: ITERATION 36
7.1 Specification 36

7.2 Analysis 36

7.3 Design 36

7.4 Charts 37

7.4 Solution code 39

7.5 Common errors 47

PART 8: ARRAYS 48
8.1 Specification 48

8.2 Analysis 48

8.3 Design 48

3
8.4 Charts 48

8.5 Solution code 48

PART 9: LISTS 53
9.1 Specification 53

9.2 Analysis 53

9.3 Design 53

9.4 Charts 54

9.5 Solution code 54

PART 10: INHERITANCE 62


10.1 Specification 62

10.2 Analysis 62

10.3 Design 62

10.4 Charts 62

10.5 Solution code 62

PART 11: POLYMORPHISM 68


11.1 Specification 68

11.2 Analysis 68

11.3 Types of account 68


11.3.1 Focus: account balance 69
11.3.2 Focus: account id 70
11.3.3 Focus: interest rate 71
11.3.4 Focus: an interactive account 72
11.3.5 Focus: withdraw 72

11.4 Storing the accounts 74

11.5 Inheritance chart 74

11.6 Client chart 75

11.7 Class diagrams 76

11.8 Solution code 76

PART 12: COMPLEX INHERITANCE 97

4
12.1 Specification 97

12.2 Analysis 97

12.3 Design: list storage and retrieval 97

12.4 Design: an inherited MENU 97

12.5 Solution code 98

PART 13: CONSTRAINED GENERICITY 104


13.1 Specification 104

13.2 Analysis 104

13.3 Design: a keyed list 104

13.4 Charts 104

13.5 Design: a keyed, storable list 104

13.6 Solution code 105

PART 14: THE COMPLETE BANK SYSTEM 110


14.1 Specification 110

14.2 Inheritance charts 111

14.3 Client charts 112

14.4 Class diagrams 113

14.5 Class listings 115

5
Part 1: Look and feel

1.1 Specification
Read and show the balance of a bank account.

1.2 Analysis
The balance is a REAL value stored in a class ACCOUNT.

1.3 Solution design


Separate routines are used to read in, and to write out, the balance. The creation routine calls these
routines. The get routine reads in a value from the user and stores it in the variable balance. The show
routine writes the value of balance to the terminal screen. Class ACCOUNT is the root class for a system of
one class, so the make routine calls every other routine in the class:

class ACCOUNT

feature
balance: REAL

make is
do
get
show
end -- make

get is ...

show is ...

end -- class ACCOUNT


The creation routine can be used by any client, so its creation policy is ANY:

creation {ANY}
make

The attribute balance and the routines that get and show the attribute are private:

feature {NONE}
balance ...

get is ...

show is ...

6
1.4 Client chart

ACCOUNT

1.5 Ace file


The Ace file to compile this system of one class is shown below. The name of the executable file
is bank. The name of the root class is ACCOUNT. The name of the creation routine in the root class is
make.

The system uses a precompiled set of Eiffel classes, and three clusters that contain files to be
compiled. The current directory (/) contains the user-defined class ACCOUNT. Two Eiffel library
directories are used to compile the system, the kernel directory and the support directory.

system
bank

root
ACCOUNT: "make"

default
assertion (require);
precompiled ("$EIFFEL3/precompiled/spec/$PLATFORM/base")

cluster
eiffel: "./";
kernel: "$EIFFEL3/library/base/kernel";
support: "$EIFFEL3/library/base/support";

end

7
1.6 Solution code

class ACCOUNT

creation {ANY}
make

feature {NONE}
balance: REAL

get_balance is
-- read the initial account balance, store it
do
io.putstring (" Initial account balance: $")
io.readreal
balance := io.lastreal
end -- get_balance

show_balance is
-- show the balance
do
io.putstring ("%NThe balance is $")
io.putreal (balance)
end -- show_balance

feature {ANY}
make is
-- use the account
do
get_balance
show_balance
end -- make

end -- class ACCOUNT

8
Part 2: Data flow

2.1 Specification
Execute the following sequence of actions on a bank account:

1. Read in and show the balance of a bank account.


2. Deposit an amount, withdraw an amount, and show the balance. Read in the amounts from the user.
3. Add interest for one day, using an annual interest rate of 4.5%. Show the balance.

2.2 Analysis
There is one class in this system, class ACCOUNT. The data in an account are the balance and the
interest rate. The system goals are to read in and store the balance, deposit money, withdraw money, and
show the balance.

2.3 Solution design


The creation routine make contains all the code. This is not a good OO solution, but it is the only
thing we can do until routines are covered in the next chapter. The client chart is unchanged from the
previous part of the case study.

A new variable named amount is used to hold the amounts to deposit and withdraw. The amount is
read from the user, used to change the balance, and that value is not used again. The amount to deposit
(withdraw) is not part of the state of the object; the state of the object is defined by its balance and rate.
The variable amount should be local, but it has to be defined as a class attribute until routines are covered
in the next chapter.

2.4 Solution code


The listing for class ACCOUNT is given below. This is the root class for the system, with one
routine named make that contains all the executable code, so the Ace file is unchanged.

9
class ACCOUNT

creation
make

feature
balance: REAL
rate: REAL is 4.5
amount: REAL

make is
-- use the account
do
io.new_line

io.putstring ("%TInitial account balance: $")


io.readreal
balance := io.lastreal
io.putstring ("%NThe balance is $")
io.putreal (balance)

io.putstring (%Tamount to deposit: $")


io.readreal
amount := io.lastreal
balance := balance + amount

io.putstring (%Tamount to withdraw: $")


io.readreal
amount := io.lastreal
balance := balance - amount

io.new_line
io.putstring ("%NThe balance is $")
io.putreal (balance)

balance := balance + balance * (rate / 100.0) / 365.25 -- add interest


today

io.new_line
io.new_line
io.putstring ("%NThe balance is $")
io.putreal (balance)
io.new_line
end -- make

end -- class ACCOUNT

10
Part 3: Routines

3.1 Specification
The specification is unchanged from part 2 of the case study.

3.2 Analysis
The analysis is unchanged, because the specification is unchanged.

3.3 Solution design


Class ACCOUNT is the root class for a system of one class, so the make routine has to call every
other routine in the class; this is unusual. The monolithic chunk of code from the previous solution is
divided into individual routines.

The main routines in the class contain the code to deposit, withdraw, show_balance, and
add_interest. The amount of interest to add is calculated from the balance and the interest rate, so it is a
function. The daily interest rate is calculated from the annual interest rate by another function day_rate. I
have added a show routine to help with debugging.

The attribute amount has been deleted; io.lastreal is used instead.

The client chart is unchanged.

3.4 Solution code


The Ace file is unchanged. The listing for class ACCOUNT is given on the next page.

11
class ACCOUNT

creation
make

feature
balance: REAL

get_balance is
-- read the initial account balance, store it
do
io.putstring ("%TInitial account balance: $")
io.readreal
balance := io.lastreal
end -- get_balance

show_balance is
-- show the balance
do
io.putstring ("%TThe balance is $")
io.putreal (balance)
end -- show_balance

deposit is
-- read an amount and add it to the balance
do
io.putstring ("%TAmount to deposit: $")
io.readreal
balance := balance + io.lastreal
end -- deposit

withdraw is
-- read an amount and subtract it from the balance
do
io.putstring ("%TAmount to withdraw: $")
io.readreal
balance := balance - io.lastreal
end -- withdraw

rate: REAL is 4.5

interest: REAL is
-- the interest for today
do
Result := balance * day_rate
end -- interest

12
day_rate: REAL is
-- daily interest rate
do
Result := (rate / 100.0) / 365.25
end -- day_rate

add_interest is
-- add the daily interest to the balance
do
balance := balance + interest
end -- add_interest

show_rate is
-- show the annual interest rate
do
io.putstring ("%TThe interest rate is: ")
io.putreal (rate)
io.putchar (%%)
end -- show_rate

make is
-- use the account
do
io.new_line
get_balance
show_balance

io.new_line
io.new_line
deposit
withdraw
show_balance

add_interest
io.new_line
io.new_line
show_balance

io.new_line
io.new_line
end -- make

show is
-- show the balance and interest_rate
do

13
show_balance
show_rate
end -- show

end -- class ACCOUNT

3.5 Common error


A common error made by procedural programmers learning an OO language is to define a single
complex routine to change the balance. This routine gets two arguments, the change to make and the
amount to use; the change is usually indicated by an argument called flag that takes on the values + or -.
The routine header and comment are shown below:

change (flag: CHARACTER; amount: REAL) is


-- if flag is + then add the amount to balance
-- if flag is - then subtract the amount from balance

Two design rules are relevant here:


1. A routine is small and do a single thing. The change routine clearly does two
things.
2. The caller is responsible for choosing the action, and the routine executes that
choice.

A single routine change is more complex, harder to understand, harder to change, and
shifts the responsibility from the caller (where it belongs) to the called routine.

14
Part 4: Objects

4.1 Specification
The bank has two customers. A customer has a name, gender, and address. For each customer in
the bank, create an account, deposit, withdraw, and show the balance at that point, and finally add interest
and show the balance.

4.2 Analysis
A bank has two customers. A customer has a name, gender, address, and one account. An account
has a balance and an interest rate. The actions that were all in the class ACCOUNT have to be placed in the
three classes BANK, CUSTOMER, and ACCOUNT, and code has to be added to read in and set the
customer attributes.

The behaviour of ACCOUNT is changed, because the customer decides how much money to
deposit and withdraw. The amounts to use are read by code in class CUSTOMER, and passed as arguments
to the deposit and withdraw procedures in ACCOUNT.

A CUSTOMER has four attributes, the name, gender, and address of the customer, and an
account. The code to set and use these attributes is placed with the data, in class CUSTOMER..

Class BANK has two attributes, both of type CUSTOMER. Each customer is created and used. The
feature use could be placed in the bank (called by use (customer)) or in the customer (called by
customer.use). When there is a choice, it is better to place code in a supplier than a client, so the use routine
is placed in class CUSTOMER.

The system now has a new root class, BANK.

4.3 Design
The creation routine in class ACCOUNT sets the initial balance.

The creation routine for a customer reads in and sets the customer details, and then creates an
account for the customer. The class CUSTOMER has routines to deposit and withdraw, that read in an
amount to use and send this amount to the deposit and withdraw routines in class ACCOUNT as an
argument. The class CUSTOMER also has a show routine.

The Ace file now lists a new root class BANK with the code; this is the root class for the rest of the
case study.

root
BANK: "make"

4.4 Client chart


The client chart for this system of three classes is shown below. A client chart shows the client -
supplier relation between classes, so the number of objects is not shown on the chart.

15
BANK CUSTOMER ACCOUNT

4.5 Solution code

class BANK

creation
make

feature
me, you: CUSTOMER

make is
-- create and use two customers
do
!!me.make
me.show
me.use
!!you.make
you.show
you.use
io.new_line
end -- make

end -- class BANK

16
class CUSTOMER

creation
make

feature
name: STRING

get_name is
-- read in and set the name
do
io.putstring (" %TName: ")
io.readline
name := clone (io.laststring)
end -- get_name

gender: CHARACTER

get_gender is
-- read in and set the gender
do
io.putstring (%TGender (M/F): ")
io.readchar
gender := io.lastchar
io.next_line
end -- get_gender

address: STRING

get_address is
-- read in and set the address
do
io.putstring ("%TAddress: ")
io.readline
address := clone (io.laststring)
end -- get_address

account: ACCOUNT

make is
-- create the customer from data input by the user
do
io.putstring ("%NEnter the customer details%N")
get_name
get_gender

17
get_address
!!account.make
end -- make

use is
-- deposit, withdraw, and show the balance
-- add interest and show the balance
do
io.new_line
deposit
withdraw
account.add_interest
account.show_balance
io.new_line
end -- use

show is
-- show the customer details
do
io.new_line
io.putstring (name)
io.putstring (" of sex ")
io.putchar (sex)
io.putstring (" lives at ")
io.putstring (address)
account.show
end -- show

deposit is
-- read an amount from user, deposit it
local amount: REAL
do
io.putstring ("%TAmount to deposit: $")
io.readreal
amount := io.lastreal
account.deposit (amount)
end -- deposit

withdraw is
-- read an amount from user, withdraw it
local amount: REAL
do
io.putstring ("%TAmount to withdraw: $")
io.readreal
amount := io.lastreal
account.withdraw (amount)

18
end -- withdraw

end -- class CUSTOMER

19
class ACCOUNT

creation
make

feature
balance: REAL

get_balance is
-- read in a balance from the user and store it
do
io.putstring (" Initial account balance: $")
io.readreal
balance := io.lastreal
end -- get_balance

show_balance is
-- show the balance
do
io.putstring ("%NThe balance is $")
io.putreal (balance)
end -- show_balance

rate: REAL is 4.5

interest: REAL is
-- the interest for today
do
Result := balance * day_rate
end -- interest

day_rate: REAL is
-- daily interest rate
do
Result := (rate / 100.0) / 365.25
end -- day_rate

show_rate is
-- show the interest rate
do
io.putstring ("%NThe interest rate is: ")
io.putreal (rate)
end -- show_rate

make is

20
-- set the initial balance
do
get_balance
end -- make

show is
-- show the balance and interest_rate
do
show_balance
show_rate
end -- show

deposit (amount: REAL) is


-- add this amount to the balance
do
balance := balance + amount
end -- deposit

withdraw (amount: REAL) is


-- subtract this amount from the balance
do
balance := balance - amount
end -- deposit

add_interest is
-- add the daily interest to the balance
do
balance := balance + interest
end -- add_interest

end -- class ACCOUNT

21
4.6 Common errors
The single most common design error is to leave the code where the designer first thought of it, in
the form the designer first coded it. This design error is seen in three ways, discussed in more detail below.
First, no classes are used and the designer writes straight procedural code, producing an enormous make
routine in the root class. Second, code is added to the class the designer is coding, with little thought about
whether that is its correct class, producing large classes. Third, routines are coded without thought of
cutting them into smaller, reusable routines, producing large routines.

1. Omit the classes entirely. All of the code could be written in one routine in one (root) class. This
results in very short code, but ignores completely the fact that that aim is to develop a resuable OO system.

2. Leave the code where it was invented. OO design follows a three step process:
a) Define the code.
b) Place the code in a routine.
c) Place the routine in a class.

Code is placed in the same class that contains the data changed or used by the code. For data that is
changed, there is no choice: Eiffel enforces this rule. For data that is used, the decision is harder and is left
to the designer. All programmers can write the code and place it in some routine, and many novices simply
stop there without thinking about reuse.

3. Write large routines. In class CUSTOMER, for example, each attribute has its own routine to read
and set the attribute value, so each routine is simple and does a single thing. The creation routine controls
these supplier routines. It is easier to simply place all this code as a single large chunk in the creation
routine, but then the code is not reusable.

22
Part 5: Behaviour

5.1 Specification
The problem specification is unchanged.

5.2 Analysis
The problem solution is improved in three ways, by making as many features as possible private
within their class, and by adding assertions to check that the code executes as advertised.

5.2.1 Creation status


The creation status of a routine specifies who can call the routine as a creation routine. Here it is
simple: bank creates customer, and customer creates account.

5.2.2 Export policies


The default design rule is to hide everything. An attribute should definitely be hidden, along with
the routines that set and show that attribute. If an attribute has to be used outside its class, then there is no
choice and the attribute is exported. A routine should be hidden. If a routine is called from outside the class
then there is no choice, and the routine has to be exported. These rules lead to very simple class interfaces.

The only feature that should be private but is not is the routine in ACCOUNT to show the
balance. The bank wants to show the balance, so we have two choices: export the routine to BANK, or
place a feature show_balance in CUSTOMER and export the ACCOUNT routine to CUSTOMER, and the
CUSTOMER routine to BANK. Some authors (Lieberherr, 1996) believe that compound calls (that skip
classes) are bad style, because they are invisible in the middle classs definition. The price for adding a
routine is an additional routine, instead of a different export policy. The only effective feature is the one in
class ACCOUNT, so the export policy of this feature is changed.

5.2.3 Assertions
A class invariant defines what must be true about the class. Here, the balance of an account must
never be negative.

A pre-condition contrains the value of an argument. Here, constraints can be placed on the
arguments to deposit (the amount must be positive) and to withdraw (the amount must positive and less
than or equal to the balance of the account). A pre-condition cannot be placed on the creation routine,
because it receives no arguments.

All the functions in the system are simple expressions, with no post-conditions.

A creation procedure has a post-condition that describes the new state of the attributes it creates or
sets. Usually the condition asserts that the identifier contains a value (is not Void, NUL, or 0). A more
precise assertion should be made if possible: here, we can assert the exact range of values for gender,
because the valid values are known. A procedure that changes a value has a post-condition that describes
the change, by comparing the old and current values of the identifier.

23
Nothing can be asserted about the output routines, because there is no way to test the value of
the terminal screen. Nothing can be asserted about a routine call, except that the routine was called when
the system ran.

5.3 Design
Creation status, export policies, and assertions are added. The system can store invalid values and
even crash at run time with invalid input, for the current code and assertions. The next part of the case
study shows how guards are added to stop the assertions from failing.

5.4 Client chart and class diagrams


The client chart is unchanged from the previous case study, and is repeated below

BANK CUSTOMER ACCOUNT

A class diagram for each class in this version of the case study is shown below. The interface for
BANK is trivial, because the code in the class is always controlled by a single make routine. The interface
of CUSTOMER is very simple, because the use of a customer was fixed by the specification. A more
common and flexible use would require more of the CUSTOMER private features to be exported.

BANK CUSTOMER ACCOUNT

patron: CUSTOMER name: STRING balance: REAL


gender: CHARACTER interest_rate: REAL is 4.5
address: STRING
make account: ACCOUNT make
show
make deposit (amount: REAL)
show withdraw (amount: REAL)
use show_balance
add_interest

5.5 Solution code


Only routine headers and assertions are shown below; the routine bodies are unchanged.

class BANK

creation
make

feature {ANY}
patron: CUSTOMER

make is
-- create, show and use a customer

24
ensure patron_exists: patron /= Void

end -- class BANK

25
class CUSTOMER

creation {BANK}
make

feature {NONE}
name: STRING

get_name is
-- read in and set the name
ensure name_exists: name /= Void

gender: CHARACTER

get_gender is
-- read in and set the gender
ensure valid_gender: gender.upper = M or gender.upper = F

address: STRING

get_address is
-- read in and set the address
ensure address_exists: address /= Void

account: ACCOUNT

feature {BANK}
make is
-- create the customer from data input by the user
ensure account_exists: account /= Void

use is
-- deposit, withdraw, and show the balance
-- add interest and show the balance

show is
-- show the customer details

feature {NONE}
deposit is
-- read in an amount and deposit it

withdraw is
-- read in an amount and withdraw it

end -- class CUSTOMER

26
27
class ACCOUNT

creation {CUSTOMER}
make

feature {NONE}
balance: REAL

get_balance is
-- read in a balance from the user and store it

show_balance is
-- show the balance

rate: REAL is 4.5

interest: REAL is
-- the interest for today

day_rate: REAL is
-- daily interest rate

show_rate is
-- show the interest rate

feature {CUSTOMER}
make is
-- read in and set the initial balance

show is
-- show the balance and interest_rate

deposit (amount: REAL) is


-- add this amount to the balance
require
positive: amount > 0
ensure
more: balance = old balance + amount

enough (amount: REAL): BOOLEAN is


-- is there at least this amount in the account?
do
Result := amount <= balance
end -- enough

withdraw (amount: REAL) is

28
-- subtract this amount from the balance
require
positive: amount > 0
enough: enough
ensure
less:balance = old balance - amount
end -- deposit

add_interest is
-- add the daily interest to the balance

invariant
not_negative_balance: balance >= 0.0

end -- class ACCOUNT

5.6 Common errors


1. Export the attributes. This forces a client to know about the internal details of the class, and makes
a system hard to maintain and extend for that reason. The rule is to export the behaviour, and hide the
implementation. The default design choice is to hide an attribute; only if this is impossible should it be
exported.

2. Write a function that does nothing but return an attribute, and export that function. This appears to
be the convention in C++, but it is not the Eiffel convention. If an attribute has to be used outside of its
class, then export it. From outside the class, it is impossible to tell if a value was returned from an attribute
or from a function. From inside the class, an exported attribute is simpler than a private attribute plus an
exported function.

29
Part 6: Selection

6.1 Specification
Extend the system in two ways:

1. Guard the system against invalid input. If a value is invalid, then reject it (do not use the value). A
much better idea is to ask the user for a better value, but this soluton must wait the next part of the case
study, where loops are covered.
2. Us e the gender code to type out Mr. or Ms. .

6.2 Analysis
Three guards are needed on an account:
The initial balance must be positive.
A deposit must be positive.
A withdrawal must be positive, and not less than the balance.

One guard is needed on the customer:


the valid input values for a gender are M, m, F, and f.

One routine is needed to transform the gender code into a title string.

6.3 Design
A function enough is defined in class ACCOUNT to test if an account has enough money for a
withdrawal. The withdraw routine in CUSTOMER uses enough as a guard on the actions. This function is
used by CUSTOMER, because it is the clients responsibility to call the supplier (withdraw) correctly.

CUSTOMER must be able to test a precondition on an ACCOUNT routine before it is called, so


the pre-conditions never fail. A function positive is therefore added to CUSTOMER.

A test cannot be used to guard the get_balance routine, because that routine has no arguments to
guard. An invalid initial balance must be rejected. The routine is therefore split into two parts named read
and set; read reads in a value from the user and tests it, and set uses this value if it is valid. The read
routine is placed in CUSTOMER, and the set in ACCOUNT.

The client chart is unchanged. The class diagram for ACCOUNT is changed to include the new
exported feature, enough. The new class diagram for ACCOUNT is shown below.

30
ACCOUNT

balance: REAL
interest_rate: REAL is 4.5

make
display
deposit (amount: REAL)
enough (amount: REAL)
withdraw (amount: REAL)
show_balance
add_interest

31
6.4 Solution code

The changed and added code in classes CUSTOMER and ACCOUNT is shown
below.

class CUSTOMER

creation {BANK}
make

feature {NONE}

...

get_gender is
-- read in a gender code, store it if it is valid
do
read_gender
if valid_gender
then gender := io.lastchar
else
io.putstring ("%TValid codes are M, m, F, f.")
io.putstring ( Gender set to M%N)
gender := M
end
end -- get_gender

read_gender is
-- read in a gender code
do
io.putstring (" Gender (M/F): ")
io.readchar
io.next_line
end -- read_gender

valid_gender: BOOLEAN is
-- has a valid gender code been entered?
do
inspect io.lastchar.upper
when 'M', 'F' then Result := true
else Result := false
end
end -- valid_gender

32
show_gender is
-- show a title indicating the gender
do
inspect gender
when 'M', 'm' then io.putstring ("%NMr. ")
when 'F', 'f' then io.putstring ("%NMs. ")
end
end -- show_gender

...

positive (amount: REAL): BOOLEAN is


-- is the amount positive?
do
Result := amount > 0
end -- positive

feature {BANK}
make is
-- create the customer from input data
do
io.putstring (%NEnter the customer details%N%N)
get_name
get_gender
get_address
get_account
end -- make

get_account is
-- read in an amount for the balance, set the balance if possible
-- if amount is invalid, set the balance to one cent
do
io.putstring ("%TEnter the initial account balance: $")
io.readreal
if positive (io.lastreal)
then !!account.make (io.lastreal)
else
io.putstring (%TAmount must be positive. Balance set to
$0.01.%N)
!!account.make (0.01)
end
end -- make

show is
-- show the customer details

33
do
io.new_line
show_gender
io.putstring (name)
...
end -- show
...

deposit is
-- read in amount from the user, deposit it if possible
do
io.putstring ("%TEnter the amount to deposit: $")
io.readreal
if positive (io.lastreal)
then account.deposit (io.lastreal)
else io.putstring (%TAmount must be positive. No deposit made.%N)
end
end -- deposit

withdraw is
-- read in amount from the user, withdraw it if possible
do
io.putstring ("Enter the amount to withdraw: ")
io.readreal
if positive (io.lastreal)
then if account.enough (io.lastreal)
then account.withdraw (io.lastreal)
else io.putstring ("%TAmount exceeds balance. No withdrawal
made.%N")
else io.putstring ("%TAmount must be positive. No withdrawal
made.%N")
end
end -- withdraw

...

end -- class CUSTOMER

34
class ACCOUNT
...

feature {NONE}
set_balance (amount: REAL) is
-- set the balance to amount
require
positive: amount > 0
do
balance := amount
end -- set_balance

feature {CUSTOMER}
make (amount: REAL) is
-- set the balance to amount
do
set_balance (amount)
end -- make

...

enough (amount: REAL): BOOLEAN is


-- does the account contain this amount?
do
Result := balance >= amount
end -- enough

end -- class ACCOUNT

6.5 Common errors


1. Code a BOOLEAN function as an if statementsuch as the test for enough money. The if solution
corresponds to the logic that if the amount is more than the balance then there is not enough, else there is
enough money. A simpler and clearer solution is to rephrase the logic as a BOOLEAN expression, with the
view that there is enough if the balance exceeds the amount.

2. Place the code to test for enough money in the withdraw routine in class ACCOUNT. This is a bad
design decision for three reasons. First, we now have the strange situation where the customer appears to
withdraw (fully executes account.withdraw) but nothing actually happens. Second, we have a complex
ACCOUNT routine that does one of two things, depending on its argument value. Third, the responsibility
for calling a routine correctly is moved from the client (where it belongs) to the supplier.

35
Part 7: Iteration

7.1 Specification
The customer needs a password to login to the system, and a menu to use the account; these
additions simulate an automatic teller machine (ATM).

The system starts up, creates a single customer, and waits for the customer to enter their password.
The customer is allowed three attempts to enter a valid password. If no correct password is entered after
three attempts, then the system terminates. If the password is correct, then a menu of account choices is
shown, and the system reads and executes the customer's choice. Any number of transactions may be made;
processing on the account continues until the customer chooses to exit the system. Interest is then added to
the account for the customer.

The valid menu choices (upper or lower case) for the customer are

D, d Deposit
W, w Withdraw up to the total amount in the account
B, b Show the balance
Q, q Quit the system
H, h Help: Show the menu choices

7.2 Analysis
Password processing looks like a good candidate for a class, because a password is a STRING that
is stored and tested. There are three obvious features: the password attribute, a make routine to read and
store the password, and a login routine to compare the user input to the stored password. The main control
structure of a login is a loop with two possible results. The user is allowed three attempts to input the
correct password (loop). If the correct password is input then the system proceeds to the user menu (vaqlid
password), otherwise the system exits immediately (invalid password).

The menu could also be an object, although the argument is not as clear. Menu processing needs
code in some class, but it appears to not store any permanent data. A menu prompts the user for a choice,
validates the user's reply, executes the choice, and repeats this sequence. The menu choice is read and
executed but not stored. The menu encapsulates a series of operations, but does not encapsulate any
permanent data. The menu is concerned with interface details that are not central to an account, so it should
be separate from class ACCOUNT. It is unclear where a MENU class would fit into the client chart. We say
that an account has a menu, which makes MENU a supplier to ACCOUNT. However, the menu choice
controls features in ACCOUNT, which makes MENU a client of ACCOUNT. Neither solution feels quite
right. The best solution is to implement the menu by inheritance, but that solution cannot be shown until
later.

Gender input is now validated in a loop, that repeats until a valid gender code is input. All other
input validation has been omitted from the rest of the case study, for simplicity.

7.3 Design
The new BANK system has two new classes for the PASSWORD and MENU. For this version of
the system, the menu is implemented as a client of ACCOUNT and a supplier to class CUSTOMER,
because the customer uses the menu to use the account. Some of the menu choices require the account to be

36
used or modified, so these choices call code in class ACCOUNT. Other choices, such as the choice to show
the menu and exit from the system, do not use the account.

The gender input code in CUSTOMER has been extended to repeat until a valid gender code is
entered. A feature is added to CUSTOMER to control password processing. Because the account is created
in CUSTOMER but used in MENU, it is passed as an argument to the menu creation routine, and its value is
stored in the menu. Because ACCOUNT is now a supplier to MENU, the export policies on the ACCOUNT
features have to be changed accordingly.

7.4 Charts
The client chart for this system and the class diagrams are shown below.

BANK CUSTOMER PASSWORD

ACCOUNT

MENU ACCOUNT

BANK CUSTOMER PASSWORD

richie: CUSTOMER name, address: STRING password: STRING


gender: CHARACTER max_tries: INTEGER is 3
password: PASSWORD
make make
account: ACCOUNT
login
menu: MENU
valid
make
display
login
add_interest

MENU ACCOUNT

account: ACCOUNT balance: REAL


interest_rate: REAL is 4.5
make (acc: ACCOUNT) make
display
deposit (amount: REAL)
enough (amount: REAL)
withdraw (amount: REAL)
show_balance
add_interest

37
The flow of control in the system consists of two loops and a selection. A customer enters the
correct password (loop 1) before getting access to the account menu. The menu system reads and executes
customer choices (loop 2). The choice is tested and its routine is called (selection). At the end of
processing, the bank adds interest to the account.

38
7.4 Solution code
Full listings are shown for the classes BANK, PASSWORD and MENU. A partial listing that shows
the changed code is given for class CUSTOMER.

class BANK

creation
make

feature {NONE}
patron: CUSTOMER

make is
-- make a customer
-- get the customer to login and use the ATM
-- add interest to their account
do
!!patron.make
patron.login
patron.account.add_interest
end -- make

end -- class BANK

39
class CUSTOMER

creation {BANK}
make

feature {NONE}
name: STRING
...
gender: CHARACTER

get_gender is
-- loop until the user enters a valid gender code
do
from read_gender
until valid_gender
loop
io.putstring ("Invalid gender code. Valid codes are M, m, F, f. Try
again%N")
read_gender
end
gender := io.lastchar
end -- get_gender

read_gender is
-- read in a gender code
do
io.putstring (" Gender (M/F): ")
io.readchar
io.next_line
end -- read_gender

valid_gender: BOOLEAN is
-- has a valid gender code been entered?
do
inspect io.lastchar
when 'M', 'm', 'F', 'f' then Result := true
else Result := false
end
end -- valid_gender

show_gender is
-- show a title indicating the gender
do
inspect gender
when 'M', 'm' then io.putstring ("%NMr. ")
when 'F', 'f' then io.putstring ("%NMs. ")

40
end
end -- show_gender

address: STRING
...
password: PASSWORD
menu: MENU

feature {BANK}
account: ACCOUNT

make is
-- create the customer from data input by the user
do
io.putstring ("%NEnter the customer details%N")
get_name
get_gender
get_address
!!account.make
!!password.make
!!menu.make (account)
end -- make
...
login is
-- if the customer enters the valid password
-- then start the ATM menu
do
password.login
if password.valid
then menu.run
else io.putstring ("Login failure. Exiting system%N")
end
end -- login
...
end -- class CUSTOMER

41
class PASSWORD

creation {CUSTOMER}
make

feature {NONE}
password: STRING

read_word is
-- read a password from the user
do
io.putstring ("%TEnter password: ")
io.readword
end -- read_word

max_tries: INTEGER is 3

failure (tries: INTEGER): BOOLEAN is


-- has the password been tried too many times?
do
Result := tries = max_tries
end -- failure

feature {CUSTOMER}
make is
-- read and store the password
do
read_word
password := clone (io.laststring)
end -- make

login is
-- attempt to get a valid password; the user gets three tries
local tries: INTEGER
do
from
tries := 1
read_word
until valid or failure (tries)
loop
io.putstring ("Incorrect password. Try again%N")
tries := tries + 1
read_word
end
end -- login

42
valid: BOOLEAN is
-- is the input word the password?
do
Result := io.laststring.is_equal (password)
end -- valid

end -- class PASSWORD

43
class MENU

creation {CUSTOMER}
make

feature {NONE}
account: ACCOUNT

make (acc: ACCOUNT) is


-- store the account
do account := acc end -- make

feature {CUSTOMER}
run is
-- show the menu, get and execute menu choices
do
show_choices
from get_choice
until end_chosen
loop
do_choice
get_choice
end
io.putstring ("%NY'all have a nice day, hear%N")
end -- make

feature {NONE}
show_choices is
-- show the valid menu choices
do
io.putstring ("%N%TMenu choices%N")
io.putstring ("%TD%TDeposit money%N")
io.putstring ("%TW%TWithdraw money%N")
io.putstring (%TB%TShow the balance%N")
io.putstring ("%TQ%TQuit the system%N")
io.putstring ("%TH%THelp: Show the menu choices%N")
end -- show_choices

get_choice is
-- get a valid menu choice from the user
do
from read_choice
until valid_choice
loop
io.putstring ("That is not a valid choice. Try again.%N")
io.putstring ("The valid choices are D, W, B, Q, and H.%N")

44
read_choice
end
end -- get_choice

read_choice is
-- read in a menu choice
do
io.putstring ("%NEnter menu choice: ")
io.readchar
io.next_line
end -- read_choice

valid_choice: BOOLEAN is
-- has the user entered a valid choice?
do
inspect io.lastchar.upper
when 'D', 'W', 'B', 'Q', 'H'
then Result := true
else Result := false
end
end -- valid_choice

end_chosen: BOOLEAN is
-- has the user chosen to finish?
do
Result := io.lastchar.upper = 'Q'
end -- end_chosen

do_choice is
-- execute the choice made by the user
do
inspect io.lastchar.upper
when 'D'' then deposit
when 'W'' then withdraw
when 'B' then account.show_balance
when 'H'' then show_choices
end -- inspect
end -- do_choice

deposit is
-- read in amount from the user, deposit it

withdraw is
-- read in amount from the user, withdraw it if possible

end -- class MENU

45
Note: The deposit and withdraw routines were moved unchanged from CUSTOMER,
because the menu now controls their use, not the customer.

46
class ACCOUNT
...
feature {MENU}
...
enough (amount: REAL): BOOLEAN is
-- does the account contain this amount?
...

feature {BANK}
add_interest is
-- add interest to the account for today
...

end -- class ACCOUNT

7.5 Common errors


1. Try to use an account in class MENU, without setting the value of the attribute account in that
class. An object gets a reference in one of two ways. The simplest way is that an object creates another
object using a creation command. The second way is to assign a value to an attribute; the value is passed as
an argument and assigned to an attribute in the menu creation routine. If neither of these is done, then the
account identifier in the menu keeps its default value and is a Void reference.

2. Treat the password like a basic type by using the wrong equality test word = password. This
equality tests checks equality of references, and the two strings will never be the identical string so the test
will always fail. The correct test is to check for equality of string content, using the STRING equality test
is_equal, as in word.is_equal (password).

3. Implement end_chosen as an attribute. This is a bad design choice, because it increases the
number of attributes, and the number of attributes should be kept as small as possible. In addition, it stores
a temporary value as a permanent attribute in the class, which is misleading. Storing an attribute vcalue
here is unnecessary, confusing, and more complex than a function.

4. Include a branch for Q in do_choice. The loop condition tests for the quit value, and the loop
exits when the value is found. The loop body is never executed for a user input of quit.

47
Part 8: Arrays

8.1 Specification
The bank can have many customers. A customer accesses their account through an ATM
(Automatic Teller Machine). The machineprocesses customer input until a special exit code of 666 is
entered into the ATM.

8.2 Analysis
A particular customer now has to be found from the set of customers. The ATM has to read a
valid identifier, find that customer, and transfer control to that customer.

The system runs until the special end of system sentinel (666) is input to the ATM. The loop code
will thus look something like

from
until system_exit
loop
read_id
if valid_id
then process_customer
else say_error
end
end

8.3 Design
The customers are implemented as an array, so the index of the customer in the array can be used
as a unique key.

8.4 Charts
The client chart for the system is shown below. The bank creates and uses an array of customers.
A customer uses a password, and their account via the menu.

BANK ARRAY PASSWORD


CUST
[T]

MENU ACCOUNT

The class diagrams for all classes in the system are unchanged.

8.5 Solution code

48
The full class listing for BANK is shown on the next page. No other classes are changed.

49
class BANK

creation {ANY}
make

feature{NONE}
patrons: ARRAY [CUSTOMER]
count: INTEGER
end_id: INTEGER is 666

feature {ANY}
make is
-- make the customers, run the atm system, add interest
do
welcome
!!patrons.make (1, 100)
add_patrons
run_atm
add_interest
io.new_line
end -- make

feature {NONE}
welcome is
-- welcome the user
do
io.putstring("%N******************************************%
%*************")
io.putstring ("%N* Welcome to myBank,%
% where your money is my money *")
io.putstring("%N******************************************%
%*************")
end -- welcome

add_patrons is
-- add customers until the user says to stop
local patron: CUSTOMER
do
from ask_for_more
until no_more
loop
!!patron.make
count := count + 1
patrons.put (patron, count)
ask_for_more
end

50
end -- add_patrons

ask_for_more is
-- ask if their are more customers to add, read reply
do
io.putstring ("%NNew customers (Y/N) ? ")
io.readchar
io.next_line
end -- ask_for_more

no_more: BOOLEAN is
-- did the user say no more?
do
Result := io.lastchar.upper = 'N'
end -- no_more

run_atm is
-- run the ATM and teller until system is shut down
do
show_atm_header
from read_id
until end_input
loop
if valid_id
then patrons.item (io.lastint).login
else io.putstring ("%TInvalid customer id. Try again.%N%N")
end
read_id
end
end -- run_atm

show_atm_header is
-- show start of atm system
do
io.putstring ("%N************************************")
io.putstring ("%N* ATM system operational *")
io.putstring ("%N************************************%N%N")
end -- show_atm_header

read_id is
-- ask for a user id, read reply
do
io.putstring ("%TEnter customer id: ")
io.readint
end -- read_id

51
end_input: BOOLEAN is
-- was the end id input?
do Result := io.lastint = end_id end -- end_input

valid_id: BOOLEAN is
-- is the input id a valid customer index?
do Result := io.lastint <= count end -- valid_id

add_interest is
-- add interest for every customer
local i: INTEGER
do
from i := 1
until i > count or else patrons.item (i) = Void
loop
patrons.item (i).account.add_interest
i := i + 1
end
end -- add_interest

end -- class BANK

52
Part 9: Lists

9.1 Specification
Each customer has a unique integer key; successive integers are used for each customer.

The bank runs over an extended period. At the start of each day, a bank teller adds interest to
every account and then creates new customers; customers are never deleted. The ATM then runs all day,
handling multiple customers. Entry of the special key value of 999 into the ATM (at the end of a day) shuts
down the whole system.

9.2 Analysis
There are now two sub-systems in the bank, one where the teller creates accounts and a second
where a customer accesses their account through the ATM. The two sub-systems are candidates for new
objects, because they encapsulate different functionality and may contain their own data: both the teller and
ATM systems use the same set of customers. The teller has to add customers to the set, and the ATM has to
find a customer given an input key.

The daily cycle has three parts. The teller creates new accounts. The ATM starts up and continues
until either the day end sentinel (666) is input, or the system end sentinel (999) is input. Interest is then
added to all accounts. The top level of the daily cycle is thus

from
until day_exit or system_exit
loop
teller
atm
add_interest
end
The teller system contains one loop to create new customers. The ATM system contains two
embedded loops. There is a loop around customers, because the ATM handles multiple customers, one at a
time. Within each customer, there is a loop around transactions, because the customer can issue as many
commands as they desire. The interest code contains one loop to cycle through the customers.

The ATM system gets the customer identifier and password, and checks to see if they are valid. If
they are valid, the account menu is then presented and the customer uses their account. If the key or
password are invalid, then the customer has to input new values. The key input to the ATM has three
functions: it may identify a customer, it may signal that the ATM is to be shut down for the day (value
666), or it may close the whole system down (value 999).

9.3 Design
The customers are stored in a LINKED_LIST because they are indexed by a unique key. The list is
created by the BANK, and passed to the teller and ATM subsystems. Because these two subsystems contain
data (the customers), they deserve to be separate classes. The BANK system initially makes an empty list,
passes the list as an argument to the teller to create new customers, and then passes the list tot he ATM to
process customer requests.

53
9.4 Charts
The client chart for the new part of the system is shown below. The BANK creates and uses a
LINKED_LIST of CUSTOMERs, and the two classes TELLER and ATM . These classes use the same list of
customers as the BANK. A customer uses a password, and their account via the menu. The suppliers to
CUSTOMER have not changed, so that part of the chart is not shown.

LINKED
BANK _LIST [T] CUST

TELLER LINKED
CUST
_LIST [T]

LINKED
ATM _LIST [T] CUST

The class diagrams for the changed classes in the system are shown below. Processing within a
customer is the same as before; in the CUSTOMER class diagram, INTEGER has been abbreviated to I
and BOOLEAN to B for clarity.

BANK TELLER ATM

patrons: L_L [CUSTOMER] patrons: L_L [CUSTOMER] patrons: L_L [CUSTOMER]


teller: TELLER end_atm: INTEGER is 666
atm: ATM end_system: INTEGER is 999
make make make
run run

CUSTOMER

name, address: STRING


gender: CHARACTER
password: PASSWORD
account: ACCOUNT
menu: MENU
make
display
login
add_interest
match (id: I): B

9.5 Solution code

54
Listings are given below for the classes BANK, TELLER, and ATM; where a feature has not
changed, only the feature header and comment are shown. The changed code in CUSTOMER to handle the
customer id is also shown.

55
class BANK

creation
make

feature{NONE}
patrons: LINKED_LIST [CUSTOMER]
teller: TELLER
atm: ATM

make is
-- make the list of customers, pass it to the ATM and TELLER, run the
system
do
greeting
!!patrons.make
!!teller.make (patrons)
!!atm.make (patrons)
run
io.putstring ("%NExit banking system%N")
end -- make

feature {NONE}
greeting is
-- welcome the user

run is
-- run the ATM and teller until system is shut down
do
from
until atm.system_finished
loop
teller.run
atm.run
add_interest
end
end -- make

add_interest is
-- add interest to every customers account

end -- class BANK

56
class TELLER

creation {BANK}
make

feature {NONE}
patrons: LINKED_LIST [CUSTOMER]

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

run is
-- add interest to all accounts, create new customers
do
show_header
new_customers
end -- run

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

new_customers is
-- add any new customers, with unique customer key
local patron: CUSTOMER
do
io.putstring ("%NAdd new customers%N")
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
-- ask for more customers, read a reply from the user
do

57
io.putstring ("%NAny new customers (Y/N)? ")
io.readchar
io.next_line
end-- ask_for_more_customers

no_more: BOOLEAN is
-- has the user said there are no more customers to add?
do
Result := io.lastchar.upper = 'N'
end -- no_more

end -- class TELLER

58
class ATM

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

end_atm: INTEGER is 666

atm_finished: BOOLEAN is
-- has the ATM finished for the day?
do Result := io.lastint = end_atm end -- atm_finished

end_system: INTEGER is 999

feature {BANK}
run is
-- run the ATM menu until the bank shuts it down
do
from read_id
until atm_finished or system_finished
loop
serve_customer
read_id
end
io.putstring ("%NExiting ATM system%N")
end -- run

system_finished: BOOLEAN is
-- has the system shutdown code been input?
do
Result := io.lastint = end_system
end -- system_finished

feature {NONE}
read_id is
-- get a customer's user identifier
do
io.putstring ("%NEnter user id: ")
io.readint
end -- read_id

59
serve_customer is
-- find the customer with the current input id
-- if the customer exists, transfer control
do
find (io.lastint)
if found then patrons.item.login
else io.putstring ("%NThat is not a valid userId")
end
end -- serve_customer

find (id: INTEGER) is


-- set the cursor at the person with this key
-- if no such person, cursor is offright
do
from patrons.start
until patrons.after or else patrons.item.match (id)
loop patrons.forth
end
end -- find

found: BOOLEAN is
-- is the cursor in the list?
do
Result := not patrons.after
end -- found

end -- class ATM

60
class CUSTOMER
...
feature {NONE}
...
id: INTEGER

set_id (key: INTEGER) is


-- set the id to key
do id := key end -- set_id

show_id is
-- show the customer identifier
do
io.putstring ("%NCustomer #")
io.putint (id)
io.putstring (": ")
end -- show_id

feature {TELLER}
make (id: INTEGER) is
-- set the customer details
do
...
set_id (key)
end -- make

show is
-- show the customer's personal details
do
show_id
...
end -- show

feature {ATM}
match (id: INTEGER): BOOLEAN is
-- does the customer key match this id?
do
Result := id = key
end -- match

end -- class CUSTOMER

61
Part 10: Inheritance

10.1 Specification
The specification is unchanged.

10.2 Analysis
There is no analysis because the specification is unchanged.

10.3 Design
The class CUSTOMER is split into two classes to separate the behaviour that belongs to a
PERSON, and to a CUSTOMER. This allows other types of customer to be easily added in the future, such
as corporations or customers with a joint account.

The attributes of a person are name, gender, and address. These are placed in a parent class
PERSON, with the routines that set and use this data. The remaining attributes and routines are specific to a
customer, so are placed in the child class CUSTOMER.

A CUSTOMER is a PERSON with a bank account, a unique identifier, and a password. The
routines associated with these three attributes live in the class CUSTOMER. In the banking system, an
object of type PERSON is never created, only customers. Class PERSON has thus been defined with an
empty creation routine, so objects of this type cannot be created.

10.4 Charts
The inheritance chart and class diagrams for CUSTOMER and PERSON are shown below.

PERSON CUSTOMER

name, address: STRING password: PASSWORD


PERSON gender: CHARACTER account: ACCOUNT
menu: MENU

make make
display display
CUSTOMER add_interest
match (id: I): B
login

The client chart is unchanged by inheritance, so it is not shown here.

10.5 Solution code


The existing CUSTOMER code is divided into the two classes PERSON and CUSTOMER. The
new code for this version is shown below; where a routine is moved as a unit and is thus unchanged, only
the routine header is shown.

62
63
class PERSON

creation

feature {NONE}
name: STRING

get_name is
-- read in and set the name

gender: CHARACTER

get_gender is
-- loop until the user enters a valid gender code

read_gender is
-- read in a gender code

valid_gender: BOOLEAN is
-- has a valid gender code been entered?

address: STRING

get_address is
-- read in and set the address

feature {ANY}
make is
-- set the personal details
do
io.putstring ("%NEnter the personal details%N")
get_name
get_gender
get_address
end -- make

show is
-- show the personal details
do
io.putstring ("%N ")
show_title
io.putstring (name)
io.putstring (" lives at ")
io.putstring (address)
end -- show

64
end -- class PERSON

65
class CUSTOMER

inherit
PERSON
rename
make as person_make,
show as person_show
end

creation {TELLER}
make

feature {NONE}
id: INTEGER

set_id (key: INTEGER) is


-- set the id to key
do id := key end -- set_id

show_id is
-- show the customer identifier
do
io.putstring ("%NCustomer #")
io.putint (id)
io.putstring (": ")
end -- show_id

password: PASSWORD
account: ACCOUNT
menu: MENU

feature {TELLER}
make (id: INTEGER) is
-- set the customer details
do
io.putstring ("%NEnter the customer details%N")
person_make
!!account.make
!!password.make
!!menu.make (account)
end -- make

show is
-- show the customer details
do
person_show

66
io.putstring ("%NThe account details are:%N")
account.show
end -- show

feature {ATM}
match (id: INTEGER): BOOLEAN is
-- does the customer key match this id?

end -- class CUSTOMER

67
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 xx
rate o oo
make x xo
deposit x x
withdraw o o
show balance x x
display o oo
left x
mature? x
interest x xx

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

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

ACCOUNT
balance
rate
display
INVEST interest INTERACCT
period
3 rates make
make deposit
new_day withdraw *
mature SAVING balance CHEQUE
display rate = 4.5 display rate = 0.0
withdraw withdraw

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

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

70
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

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

72
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)

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

74
ACCOUNT MENU

INTER-
INVEST INTEREST
ACCT

SAVING CHEQUE

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

LINKED
BANK _LIST [T] CUST

LINKED
TELLER CUST
_LIST [T]

LINKED
ATM _LIST [T] CUST

CUST PASSWORD

ACCOUNTS SAVINGS

CHEQUE

INVEST

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

CUSTOMER ACCOUNTS

id: INTEGER savings: SAVINGS


password: PASSWORD cheque: CHEQUE
accounts: ACCOUNTS invest: INVEST

make make
show show
match (id: I): BOOLEAN use
login end_day

ACCOUNT INTEREST INVEST

id: INTEGER rate: REAL minimum: REAL i s 1000.0


balance: REAL period: INTEGER
days: INTEGER

make make make


show add_interest show
new_day
mature: BOOLEAN

INTERACCT SAVINGS CHEQUE

deposit (amount: REAL)


enough (amount:R): B
withdraw (amount: REAL)

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

76
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

77
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
-- prompt the user for more accounts, read reply
do
io.putstring ("%NNew accounts for customers (Y/N)? ")
io.readchar
io.next_line
end -- ask_for_more_accounts

read_id is
-- get a customer's user identifier
do
io.putstring ("%N%TEnter user id: ")
io.readint
end -- read_id

find (key: INTEGER) is


-- set the cursor at the person with this key
-- if no such person, cursor is offright

78
do
from patrons.start
until
patrons.after
or else patrons.item.match (key)
loop patrons.forth
end
end -- find

found: BOOLEAN is
-- is the cursor in the list?
do
Result := not patrons.after
end -- found

end -- class TELLER

79
class CUSTOMER

inherit
PERSON
rename
make as make _person,
show as show _person
end

creation
make
...
feature {BANK, TELLER}
accounts: ACCOUNTS

feature {TELLER}
make (key: INTEGER) is
-- set the customer details
do
make_ person
set_id (key)
!!password.make
!!accounts.make
end -- make

show is
-- show the customer details
do
show_ person
show_id
accounts.show
end -- show

feature {ATM, TELLER}


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

feature {ATM}
login is
-- if the customer enters the valid password
-- then get them to choose an account
do
password.login

80
if password.valid
then accounts.use
else io.putstring ("Login failure. Exiting system%N")
end
end -- login

end -- class CUSTOMER

81
class ACCOUNTS

creation {CUSTOMER}
make

feature {NONE}
savings: SAVINGS
cheque: CHEQUE
invest: INVEST

feature {CUSTOMER}
make is
-- create one or more accounts
local key: CHARACTER
do
from
until no_more
loop
get_key
key := io.lastchar.upper
if exists (find (key))
then io.putstring ("%N%TCustomer has that type. Try again")
else create (key)
end
ask_for_more
end
end -- make

show is
-- show the details for existing accounts
do
io.putstring ("%TThe accounts are:")
if exists (savings) then savings.show end
if exists (cheque) then cheque.show end
if exists (invest) then invest.show end
end -- show

feature {BANK}
end_day is
-- add interest to savings account
-- add a day to the investment account counter, add interest to investment
-- if the investment account is mature, transfer it
do
if exists (savings) then savings.add_interest end
if exists (invest) then
invest.add_interest

82
invest.new_day
if invest.mature then transfer end
end
end -- end_day

feature {NONE}
transfer is
-- transfer the investment to the cheque account, delete the investment account
do
cheque.deposit (invest.balance)
invest := Void
end -- check_mature

get_key is
-- get a valid key for an account
do
from read_key
until valid_key (io.lastchar) or is_quit (io.lastchar)
loop
io.putstring ("%NThat is not a valid type. Try again")
read_key
end
end -- get_key

read_key is
-- read in an account type (S, C, I) from the user
do
io.putstring ("%TEnter type of account (S/C/I): ")
io.readchar
io.next_line
end -- read_key

ask_for_more is
-- ask the user if they want to do more
do
io.putstring ("%NMore accounts (Y/N)? ")
io.readchar
io.next_line
end -- ask_for_more

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

find (key: CHARACTER): ACCOUNT is


-- this type of account, or Void

83
do
if is_savings (key) then Result := savings
elseif is_cheque (key) then Result := cheque
elseif is_invest (key) then Result := invest
end
end -- find

create (key: CHARACTER) is


-- create an account of this type
-- for an investment account, ensure there is a cheque account
do
if is_savings (key) then !!savings.make
elseif is_cheque (key) then !!cheque.make
elseif is_invest (key) then
!!invest.make
if not exists (cheque) then !!cheque.make_zero end
end
end -- create

feature {CUSTOMER}
use is
-- select an account to use, show the account menu
-- loop until user decides to leave
local key: CHARACTER
do
from get_atm_key
until is_quit (io.lastchar)
loop
key := io.lastchar.upper
if exists (find (key))
then run_menu (key)
else io.putstring ("%TYou don't have that type of account%N")
end
get_atm_key
end
io.putstring ("%TY'all come back soon now, hear?")
io.putstring ("%N%N%T******************")
end -- use

feature {NONE}
get_atm_key is
-- get a valid key for an account that can be accessed via atm
do
from read_reply
until valid_reply (io.lastchar)
loop

84
io.putstring ("%NSorry, that was not a valid choice.")
io.putstring ("%NYou can only use a savings or cheque account")
io.new_line
read_reply
end
end -- get_atm_key

read_reply is
-- read in the type of account or an end code from the user
do
io.putstring ("%TEnter type of account, or quit (S/C/Q): ")
io.readchar
io.next_line
end -- read_reply

valid_reply (key: CHARACTER): BOOLEAN is


-- was the input valid?
do
Result := is_savings (key) or is_cheque (key) or is_quit (key)
end -- valid_reply

run_menu (key: CHARACTER) is


-- run the menu for this type of account
do
inspect key
when 'S' then savings.menu
when 'C' then cheque.menu
end
end -- use_account

valid_key (key: CHARACTER): BOOLEAN is


-- is this a valid account key?
do
Result := is_savings (key) or is_cheque (key) or is_invest (key)
end -- valid_type

is_savings (key: CHARACTER): BOOLEAN is


-- does the key match the savings account id?
do Result := key.upper = 'S' end -- is_savings

is_cheque (key: CHARACTER): BOOLEAN is


-- does the key match the cheque account id?
do Result := key.upper = 'C' end -- is_cheque

is_invest (key: CHARACTER): BOOLEAN is


-- does the key match the cheque account id?

85
doResult := key.upper = 'I' end -- is_invest

is_quit (key: CHARACTER): BOOLEAN is


-- does the key match the quit code?
do Result := key.upper = 'Q' end -- is_quit

exists (object: ANY): BOOLEAN is


do Result := object /= Void end -- exists

end -- class ACCOUNTS

86
deferred class ACCOUNT

feature {NONE}
id: CHARACTER

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

match (key: CHARACTER): BOOLEAN is


-- does this key match the account id?
do Result := key = id end -- match

show_id is
-- show the type of acccount
do
io.putstring ("%N%TAccount type is ")
io.putchar (id)
end -- show_id

balance: REAL

get_balance is
-- get the balance of the account from the user, store it
do
io.putstring ("%TEnter initial account balance: ")
io.readreal
balance := io.lastreal
end -- get_balance

show_balance is
-- show the balance
do
io.putstring ("%N%TThe balance is $")
io.putreal (balance)
end -- show_balance

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

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

end -- class ACCOUNT

88
deferred class INTEREST

inherit
ACCOUNT
redefine make
end

feature {NONE}
rate: REAL

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

interest: REAL is
-- interest for today
do
Result := balance * day_rate
end -- interest

day_rate: REAL is
-- daily interest rate
do
Result := (rate / 100) / 365.25
end -- day_rate

feature {ACCOUNTS}
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
do
balance := balance + interest
end -- add_interest

end -- class INTEREST

89
class INVEST

inherit
INTEREST
export {ACCOUNTS} balance
redefine make, show
end

creation { ACCOUNTS }
make

feature {NONE}
minimum: REAL is 1000.0

get_min_balance is
-- get a balance of at least minimum
do
from get_balance
until valid_balance
loop
io.putstring ("%TInitial balance must be at least $1000.%N%N")
get_balance
end
end -- get_min_balance

valid_balance: BOOLEAN is
-- is the balance at least minimum?
do
Result := balance >= minimum
end -- valid_balance

set_id is
-- set the account id to 'I'
do id := 'I' end -- set_id

set_rate is
-- set the interest rate from the period
do
inspect period
when 3 then rate := 5.5
when 6 then rate := 6.0
when 12 then rate := 6.5
end
end -- set_rate

period: INTEGER

90
get_period is
-- set the period of the account
do
io.putstring ("Enter period (3/6/12): ")
io.readint
period := io.lastint
end -- get_period

show_period is
-- show the period
do
io.putstring ("%N The period is ")
io.putint (period)
io.putstring (" months")
end -- show_period

days: INTEGER

show_elapsed is
-- show the number of days elapsed in the period
do
io.putstring ("%N The account has run for ")
io.putint (days)
io.putstring (" days")
end -- show_elapsed

feature {ACCOUNTS}
make is
-- set the id, balance, period, and interest rate
do
set_id
get_balance
get_period
set_rate
end -- make

show is
-- show the balance, interest rate, period, and day counter
do
io.putstring ("%N***Investment account***")
show_balance
show_period
show_elapsed
end -- display

91
new_day is
-- increment the day counter
do
days:= days + 1
end -- new_day

mature: BOOLEAN is
-- is the account mature?
do
Result := days = period * 30
end -- mature

invariant
min_balance: balance >= minimum

end -- class INVEST

92
deferred class INTERACCT

inherit
ACCOUNT
MENU

feature {ACCOUNTS}
deposit (amount: REAL) is
-- add amount to balance
do
balance := balance + amount
end -- deposit

enough (amount: REAL): BOOLEAN is


-- does the account contain this amount?
do
Result := balance >= amount
end -- enough

withdraw (amount: REAL) is


-- subtract this amount from the balance
do
balance := balance - amount
end -- withdraw

end -- class INTERACCT

93
class SAVINGS

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

creation {ACCOUNTS}
make

feature {NONE}
set_id is
-- set the account id to 'S'
do id := 'S' end -- set_id

set_rate is
-- set the interest rate for savings account
do rate := 4.5 end -- set_rate

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

end -- class SAVINGS

94
class CHEQUE

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

creation {ACCOUNTS}
make, make_zero

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

set_id is
-- set the account id to 'C'
do id := 'C' end -- set_id

make_zero is
-- create the account with zero balance
do set_id end -- make_zero

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

penalise is
-- tell the user the transaction failed, apply the penalty
do
io.putstring ("%NInsufficient funds")
if balance >= penalty
then balance := balance - penalty
else balance := 0
end
end -- apply_penalty

95
end -- class CHEQUE

96
Part 12: Complex inheritance

12.1 Specification
"Read the system data from file every morning when the system starts up, and write it to file every
night when the system shuts down."

12.2 Analysis
Two changes are made to the system. The first change adds file storage and retrieval. The second
change uses multiple inheritance to separate the menu from the account classes.

12.3 Design: list storage and retrieval


The list of customers is stored and retrieved by inheriting the class STORABLE in the BANK (to
retrieve) and in the list class (to store). The inheritance chart for this part is:

LINKED_
STORABLE
LIST

STORE
BANK
_LIST

12.4 Design: an inherited MENU


The menu to an account should be separate from the account actions. This is done by deferring the
actions in the MENU, effecting the features in the appropriate account, and joining the deferred and
effective features by multiple inheritance. The class INTERACCT contains the effective features, so it will
inherit the deferred MENU. The account inheritance hierarchy is:

ACCOUNT MENU

INTER- INTER-
EST ACCT

INVEST SAVING CHEQUE

97
12.5 Solution code
Part of BANK, TELLER, ATM, MENU, and all of STORE_LIST are shown below.

98
class BANK

inherit
STORABLE

creation
make

feature {NONE}
file_name: STRING is "patrons.dat"
patrons: STORE_LIST[CUSTOMER]
teller: TELLER
atm: ATM

make is
-- get the list of customers, run the system, store the data to file
do
retrieve
!!teller.make (patrons)
!!atm.make (patrons)
run
store
end -- make

retrieve is
-- make or retrieve the list of customers
local file: RAW_FILE
do
!!file.make (file_name)
if file.exists then
file.open_read
patrons ?= retrieved (file)
file.close
show_retrieved
end
if patrons = Void then !!patrons.make end
end -- retrieve

show_retrieved is
-- show the number of records read from file
do
io.putstring (%N%T**** )
io.putint (patrons.count)
io.putstring ( records read from file ****%N)
end -- show_retrieved

99
store is
-- store the list of customers
local file: RAW_FILE
do
!!file.make (file_name)
file.open_write
patrons.basic_store (file)
file.close
end -- store

end -- class BANK

100
class STORE_LIST [T]

inherit
STORABLE
LINKED_LIST [T]

creation
make

end -- class STORE_LIST

class TELLER

creation {BANK}
make

feature {NONE}
patrons: STORE_LIST [CUSTOMER]

feature {BANK}
make (customersd: STORE_LIST [CUSTOMER]) is
-- store the list of customers
do patrons := customers end -- make
...
end -- class TELLER

class ATM

creation {BANK}
make

feature {NONE}
patrons: STORE_LIST [CUSTOMER]

feature {BANK}
make (customersd: STORE_LIST [CUSTOMER]) is
-- store the list of customers
do patrons := customers end -- make
...
end -- class ATM

101
deferred class MENU

feature {ACCOUNTS}
menu is
-- show the menu
-- get and execute menu choices

feature {NONE}
do_choice is
-- execute the choice made by the user
do
inspect io.lastchar.upper
when 'D' then do_deposit
when 'W' then do_withdraw
when 'B' then show_balance
when 'H' then show_choices
end -- inspect
end -- do_choice

do_deposit is
-- get the amount to deposit, then deposit it
local amount: REAL
do
io.putstring ("Enter the amount to deposit: ")
io.readreal
deposit (io.lastreal)
end -- do_deposit

do_withdraw is
-- get the amount to withdraw
-- if there is enough money, withdraw the amount
local amount: REAL
do
io.putstring ("Enter the amount to withdraw: ")
io.readreal
amount := io.lastreal
withdraw (amount)
end -- do_withdraw

feature {NONE}
deposit (amount: REAL) is
-- add the amount to the balance
deferred
end -- deposit

withdraw (amount: REAL) is

102
-- withdraw this amount if there is enough money
deferred
end -- withdraw

show_balance is
-- display the curent balance
deferred
end -- show_balance

end -- class MENU

103
Part 13: Constrained genericity

13.1 Specification
The specification is unchanged from the previous case study.

13.2 Analysis
There is no analysis because the specification is unchanged.

13.3 Design: a keyed list


Constrained genericity is used to move the list scan code from ATM and TELLER to KEY_LIST
[T]. Class KEY_LIST can be used to store and retrieve any object with an integer key. The minimum that
we need is to define a keyed list of customers, but a more general solution is shown that works for any
object with an INTEGER key. The construction of a class that is a keyed list requires a deferred class that
has a key and a match routine (class KEY) a child that inherits this parent and effects the match routine
(class CUSTOMER) and a constrained generic class (class KEY_LIST) The constrained class has KEY as its
constraint in the class header, and contains code to look up an element of the list using a key.

13.4 Charts

LINKED_
KEYED
LIST

KEY_
CUST
LIST

KEY_LIST CUST-
BANK
[T] OMER

13.5 Design: a keyed, storable list


A keyed, storable list class is created by combining the inheritance hierarchies from the two
applications, and moving the lookup code from the clients (TELLER and ATM) into the keyed class. The
inheritance hierarchy for the keyed, storable list class is shown below.

104
LINKED_ PERSON
STORABLE KEYED
LIST

BANK KEY_LIST CUST-


OMER

13.6 Solution code

A full listing for class KEYED is shown below, then its inheritance in
CUSTOMER The following pages shows the client code to use a keyed list in TELLER
and ATM, followed by a full listing for KEY_LIST .

class KEYED

feature {NONE}
id: INTEGER

feature {ANY}
set_id (key: INTEGER) is
-- store the key in id
do
id := key
end -- set_id

match (key: INTEGER): BOOLEAN is


-- does the key match the id?
do
Result := key = id
end -- match

show_id is
-- show the customer id
do
io.putstring ("%NThe customer id is ")
io.putint (id)
end -- show_id

end -- class KEYED

class CUSTOMER

105
inherit
KEYED
PERSON

...

end -- class CUSTOMER

106
class TELLER

creation {BANK}
make

feature {NONE}
patrons: KEY_LIST[CUSTOMER]

make (customers: KEY_LIST [CUSTOMER]) is


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

class ATM

creation {BANK}
make

feature {NONE}
patrons: KEY_LIST [CUSTOMER]

make (customers: KEY_LIST [CUSTOMER]) is


-- store the customers in patrons
do patrons := customers end -- make
...
serve_customer (id: INTEGER) is
-- find the customer with the input id

107
-- if the customer exists, transfer control
do
patrons.find (id)
if patrons.found
then patrons.item.login
else io.putstring ("%NThat is not a valid userId")
end
end -- serve_customer
...
end -- class ATM

108
class KEY_LIST [T -> KEYED]

inherit
STORABLE
LINKED_LIST [T]

creation {BANK}
make

feature {TELLER, ATM}


get_id is
-- get a customer's user identifier
do
io.putstring ("%N%TEnter user id: ")
io.readint
end -- get_id

find (key: INTEGER) is


-- set the cursor at the person with this key
-- if no such person, cursor is offright
do
from start
until after or else item.match (key)
loop forth
end
end -- find

found: BOOLEAN is
-- is the cursor in the list?
do
Result := not after
end -- found

end -- class KEY_LIST

109
Part 14: The complete BANK system

14.1 Specification
A banking system has many customers. Each customer may have a savings account, a cheque
account, and an investment account, one account of each type. The bank offers access to cheque and
savings accounts through an interactive menu like that seen in an automatic teller machine (ATM); an
investment account cannot be accessed through the ATM. Savings and investment accounts accrue daily
interest, paid on the current balance; cheque accounts do not accrue interest.

A positive amount may be deposited in an account or withdrawn from an account. A withdrawal


from a savings account decrements the balance by that amount; there are no charges or penalties for a
savings account. A successful withdrawal from a cheque account costs 50 cents. An unsuccessful
withdrawal from a cheque account costs $5. The balance of an account cannot be negative.

A savings account gets daily interest; the interest rate is 4.5% a year. A cheque account gets no
interest. An investment account is created with an initial balance of at least $1000, 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 bank system runs for an extended period. At the start of each day, a bank teller creates new
customers, and new accounts for existing customers. Each customer has a unique integer key; successive
integers are used for each new customer. Customers and accounts are never deleted from the bank. The
ATM then runs all day, handling multiple customers. To use the ATM, a customer enters their unique key
(this simulates putting a card into the ATM) and their password, chooses an account, then chooses
commands from the menu. Menu commands are read and executed until the customer finishes; the ATM
then waits for the next customer. A special key of 666 exits the ATM system for the day. Interest is then
added to all savings and investment accounts. Entry of the special key value of 999 into the ATM shuts
down the whole system. The bank data is stored to file when the system shuts down, and is retrieved from
file when the system starts up again.

A customer is allowed three attempts to login to the ATM by entering a valid password. If no
correct password is entered after three attempts, then the ATM system rejects the login attempt and asks for
a new customer identifier. If the password is correct, then the customer is shown a menu of account
choices, and the system reads and executes the choices. Any number of transactions may be made;
processing on the account continues until the customer chooses to exit that account. Multiple accounts may
be chosen and used within a single ATM session.

The ATM menu choices (upper or lower case) are

D Deposit
W Withdraw up to the total amount in the account
B Show the balance
Q Quit the system
H Help: Show the menu choices

110
14.2 Inheritance charts
The first inheritance chart shows the inheritance for a storable, keyed list, and for an element of
that list, a customer.

LINKED_
STORABLE PERSON
LIST

BANK KEY_LIST CUSTOMER

The second inheritance chart shows the inheritance structure of the bank accounts.

ACCOUNT MENU

INTEREST INTER-
INVEST
ACCT

SAVING CHEQUE

111
14.3 Client charts
The first client chart shows the overall structure of the system that uses a list of customers.

BANK KEY_LIST CUST-


[T] OMER

TELLER KEY_LIST CUST-


[T] OMER

KEY_LIST CUST-
ATM
[T] OMER

The second part of the client chart shows the use within a customer.

CUST-
PASSWORD
OMER

ACCOUNTS SAVINGS

CHEQUE

INVEST

112
14.4 Class diagrams
The first set of three diagrams shows the overall structure of the banking system.

BANK

file_name: STRING is "patrons.dat"


patrons: KEY_LIST[CUSTOMER]
teller: TELLER
atm: ATM

make

TELLER ATM

patrons: KEY_LIST [CUSTOMER] patrons: KEY_LIST [CUSTOMER]


end_atm: INTEGER is 666
end_system: INTEGER is 999

make make
run run
system_finished

The next set of five diagrams shows the customer subsystem.

KEY_LIST [T -> KEYED] KEYED

id: CHARACTER

read_id set_id
find (key: CHARACTER) match (key: C): BOOLEAN
found: BOOLEAN show_id

PERSON CUSTOMER PASSWORD

name: STRING password: PASSWORD password: STRING


gender: CHARACTER accounts: ACCOUNTS max_tries: INTEGER is 3
address: STRING
make make
make
login login
show
show valid: BOOLEAN

113
The next set of four charts shows the account container class, and the non-ATM account classes.

ACCOUNTS

savings: SAVINGS
cheque: CHEQUE
invest: INVEST

make
show
use
end_day

ACCOUNT INTEREST INVEST

id: INTEGER rate: REAL minimum: REAL i s 1000.0


balance: REAL period: INTEGER
days: INTEGER

make make make


show add_interest show
new_day
mature: BOOLEAN

The final set of four diagrams shows the ATM account classes.

ACCOUNTS

savings: SAVINGS
cheque: CHEQUE
invest: INVEST

make
show
use
end_day

ACCOUNT INTEREST INVEST

id: INTEGER rate: REAL minimum: REAL i s 1000.0


balance: REAL period: INTEGER
days: INTEGER

make make make


show add_interest show
new_day
mature: BOOLEAN

114
14.5 Class listings
The Eiffel code for the BANK system is shown on the following pages. The classes are listed in
client order, and within this in inheritance order. The client and inheritance orders are shown below, taken
from the client and inheritance charts. A total listing order is then given.

Client order

BANK TELLER KEY_LIST CUSTOMER PASSWORD


ACCOUNTS SAVINGS
CHEQUE
INVEST
ATM KEY_LIST CUSTOMER PASSWORD
ACCOUNTS SAVINGS
CHEQUE
INVEST

Inheritance order

KEYED CUSTOMER
PERSON

ACCOUNT INTEREST INVEST

ACCOUNT MENU INTERACCT INTEREST SAVINGS

ACCOUNT MENU INTERACCT CHEQUE

Listing order

BANK
TELLER
ATM
KEY_LIST
KEYED
PERSON
CUSTOMER
PASSWORD
ACCOUNTS
ACCOUNT
INTEREST
INVEST
MENU
INTERACCT
SAVINGS
CHEQUE

115
class BANK

inherit
STORABLE

creation
make

feature {NONE}
file_name: STRING is "patrons.dat"
patrons: KEY_LIST[CUSTOMER]
teller: TELLER
atm: ATM

make is
-- make or retrieve the list of customers
-- make the ATM and TELLER, pass the customer list
-- run the ATM and teller until system shuts down
do
retrieve
!!teller.make (patrons)
!!atm.make (patrons)
run
-- store
io.putstring ("%N%NExit banking system%N")
end -- make

retrieve is
-- make or retrieve the list of customers
local file: RAW_FILE
do
!!file.make (file_name)
if file.exists then
file.open_read
patrons ?= retrieved (file)
file.close
show_retrieved
end
if patrons = Void then !!patrons.make end
end -- retrieve

show_retrieved is
-- show the number of customers retrieved
do
io.putstring ("%N%T**** ")
io.putint (patrons.count)

116
io.putstring (" records read from file ****%N")
end -- show_retrieved

run is
-- each day, run the teller then the atm subsystems
-- at the end of a day, add interest and check investments
do
from
until atm.system_finished
loop
teller.run
atm.run
end_day
end
end -- run

end_day is
-- add interest for every customer
-- add a day to the investment counter
do
from patrons.start
until patrons.after
loop
patrons.item.accounts.end_day
patrons.forth
end
end -- end_day

store is
-- store the list of customers
local file: RAW_FILE
do
!!file.make (file_name)
file.open_write
patrons.basic_store (file)
file.close
end -- store

end -- class BANK

117
class TELLER

creation {BANK}
make

feature {NONE}
patrons: KEY_LIST[CUSTOMER]

feature {BANK}
make (customers: KEY_LIST[CUSTOMER]) is
-- set the patrons to the list of customers
do patrons := customers end -- make

run is
-- add interest to all accounts
-- 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)
patron.add_accounts
patrons.extend (patron)
ask_for_more_customers
end
end -- new_customers

118
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.to_upper = 'N'
end -- no_more

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

ask_for_more_accounts is
-- prompt the user for more accounts, read reply
do
io.putstring ("%NNew accounts for customers (Y/N)? ")
io.readchar
io.next_line
end -- ask_for_more_accounts

end -- class TELLER

119
class ATM

creation {BANK}
make

feature {NONE}
patrons: KEY_LIST [CUSTOMER]

end_atm: INTEGER is 666

atm_finished: BOOLEAN is
-- has the ATM finished for the day?
do Result := io.lastint = end_atm end -- atm_finished

end_system: INTEGER is 999

feature {BANK}
make (customers: KEY_LIST[CUSTOMER]) is
-- set the patrons to the list of customers
do patrons := customers end -- make

run is
-- run the ATM menu until a bank officer officer shuts it down
do
greeting
from patrons.read_id
until atm_finished or system_finished
loop
serve_customer (io.lastint)
patrons.read_id
end
io.putstring ("%NExiting ATM system%N")
end -- run

system_finished: BOOLEAN is
-- has the system shutdown code been input?
do Result := io.lastint = end_system end -- system_finished

feature {NONE}
greeting is
-- welcome the user
do
io.putstring("%N*****************************%
%**************************")
io.putstring ("%N* Welcome to myBank, where your money%
% is my money *")

120
io.putstring("%N*****************************%
%**************************")
end -- greeting

serve_customer (id: INTEGER) is


-- serve the customer with this id if possible
do
patrons.find (id)
if patrons.found
then patrons.item.login
else io.putstring ("%NThat is not a valid userId")
end
end -- serve_customer

read_id is
-- get a customer's user identifier
do
io.putstring ("%N%TEnter user id: ")
io.readint
end -- read_id

end -- class ATM

121
class KEY_LIST [T -> CUSTOMER]

inherit
STORABLE
LINKED_LIST [T]

creation {BANK}
make

feature {TELLER, ATM}


read_id is
-- get a customer's user identifier
do
io.putstring ("%N%TEnter user id: ")
io.readint
end -- read_id

find (key: INTEGER) is


-- set the cursor at the person with this key
-- if no such person, cursor is offright
do
from start
until after or else item.match (key)
loop forth
end
end -- find

found: BOOLEAN is
-- is the cursor in the list?
do
Result := not after
end -- found

end -- class KEY_LIST

122
class KEYED

feature {NONE}
id: INTEGER

feature {ANY}
set_id (key: INTEGER) is
-- store the key in id
do
id := key
end -- set_id

match (key: INTEGER): BOOLEAN is


-- does the key match the id?
do
Result := key = id
end -- match

show_id is
-- show the customer id
do
io.putstring ("%NThe customer id is ")
io.putint (id)
end -- show_id

end -- class KEYED

123
class PERSON

creation

feature {NONE}
name: STRING

get_name is
-- read in the name from the user, store it
do
io.putstring ("%TName: ")
io.readline
name := clone (io.laststring)
end -- get_name

gender: CHARACTER

get_gender is
-- loop until the user enters a valid gender
do
from read_gender
until good_gender
loop
io.putstring ("Valid codes are M, m, F, or f. Try again%N")
read_gender
end
gender := io.lastchar
end -- get_gender

read_gender is
-- read in the gender from the user
do
io.putstring ("%TGender (M/F): ")
io.readchar
io.next_line
end -- read_gender

good_gender: BOOLEAN is
-- has a valid gender code been entered?
do
inspect io.lastchar.upper
when 'M', 'F'
then Result := true
else Result := false
end
end -- good_gender

124
show_gender is
-- show a message indicating the gender
do
inspect gender
when 'M' then io.putstring ("%NMr. ")
when 'F' then io.putstring ("%NMs. ")
end
end -- show_gender

address: STRING

get_address is
-- read in the address from the user, store it
do
io.putstring ("%TAddress: ")
io.readline
address := clone (io.laststring)
end -- get_address

feature {ANY}
make is
-- set the personal details
do
io.putstring ("%NEnter the personal details%N")
get_name
get_gender
get_address
end -- make

show is
-- show the personal details
do
io.putstring ("%N ")
show_gender
io.putstring (name)
io.putstring (" lives at ")
io.putstring (address)
end -- show

end -- class PERSON

125
class CUSTOMER

inherit
KEYED
PERSON
rename
make as make_person,
display as show_person
end

creation {TELLER}
make

feature {NONE}
password: PASSWORD

feature {BANK, TELLER}


accounts: ACCOUNTS

feature {TELLER}
make (key: INTEGER) is
-- set the customer details
do
make_person
set_id (key)
!!password.make
!!accounts.make
end -- make

show is
-- show the customer details
do
show_person
show_id
accounts.show
end -- show

feature {ATM}
login is
-- if the customer enters the valid password
-- then get them to choose an account
do
password.login
if password.valid
then use_accounts
else io.putstring ("Login failure. Exiting system%N")

126
end
end -- login

end -- class CUSTOMER

127
class PASSWORD

creation {CUSTOMER}
make

feature {NONE}
password: STRING
max_tries: INTEGER is 3

feature {CUSTOMER}
make is
-- set the password
do
io.putstring ("%TPassword: ")
io.readword
io.next_line
password := clone (io.laststring)
end -- make

login is
-- attempt to get a valid password
local tries: INTEGER
do
from
read_word
tries := 1
until valid or failure (tries)
loop
io.putstring ("Incorrect password. Try again%N")
read_word
tries := tries + 1
end
end -- login

valid: BOOLEAN is
-- is the input word the password?
do
Result := io.laststring.is_equal (password)
end -- valid

feature {NONE}
read_word is
-- read in a password, add 1 to the number of attempts
do
io.putstring ("%TEnter the password: ")
io.readword

128
io.next_line
end -- read_word

failure (tries: INTEGER): BOOLEAN is


-- has the password been tried too many times?
do
Result := tries = max_tries
end -- failure

end -- class PASSWORD

129
deferred class ACCOUNT

feature {NONE}
id: CHARACTER

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

match (key: CHARACTER): BOOLEAN is


-- does this key match the account id?
do
Result := key = id
end -- match

show_id is
-- show the type of acccount
do
io.putstring ("%N%TAccount type is ")
io.putchar (id)
end -- show_id

balance: REAL

get_balance is
-- set the balance of the account
do
io.putstring ("%TEnter initial account balance: ")
io.readreal
balance := io.lastreal
end -- get_balance

show_balance is
-- show the balance
do
io.putstring ("%N%TThe balance is $")
io.putreal (balance)
end -- show_balance

feature {ANY}
make is
-- set the account id and the initial balance
do
set_id
get_balance

130
end -- make

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

end -- class ACCOUNT

131
deferred class INTEREST

inherit
ACCOUNT
redefine make
end

feature {NONE}
rate: REAL

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

interest: REAL is
-- interest for today
do
Result := balance * day_rate
end -- interest

day_rate: REAL is
-- daily interest rate
do
Result := (rate / 100) / 365.25
end -- day_rate

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

add_interest is
-- add the daily interest to the balance
do
balance := balance + interest
end -- add_interest

end -- class INTEREST

132
class INVEST

inherit
INTEREST
export {ACCOUNTS} balance
redefine make, show
end

creation { ACCOUNTS }
make

feature {NONE}
minimum: REAL is 1000.0

get_min_balance is
-- get a balance of at least minimum
do
from get_balance
until valid_balance
loop
io.putstring ("%TInitial balance must be at least $1000.%N%N")
get_balance
end
end -- get_min_balance

valid_balance: BOOLEAN is
-- is the balance at least minimum?
do
Result := balance >= minimum
end -- valid_balance

set_id is
-- set the account id to 'I'
do id := 'I' end -- set_id

set_rate is
-- set the interest rate from the period
do
inspect period
when 3 then rate := 5.5
when 6 then rate := 6.0
when 12 then rate := 6.5
end
end -- set_rate

period: INTEGER

133
get_period is
-- set the period of the account
do
io.putstring ("Enter period (3/6/12): ")
io.readint
period := io.lastint
end -- get_period

show_period is
-- show the period
do
io.putstring ("%N The period is ")
io.putint (period)
io.putstring (" months")
end -- show_period

days: INTEGER

show_elapsed is
-- show the number of days elapsed in the period
do
io.putstring ("%N The account has run for ")
io.putint (days)
io.putstring (" days")
end -- show_elapsed

feature {ACCOUNTS}
make is
-- set the id, balance, period, and interest rate
do
set_id
get_min_balance
get_period
set_rate
end -- make

show is
-- show the balance, interest rate, period, and day counter
do
io.putstring ("%N***Investment account***")
show_balance
show_period
show_elapsed
end -- show

134
new_day is
-- increment the day counter
do
days:= days + 1
end -- new_day

mature: BOOLEAN is
-- is the account mature?
do
Result := days = period * 30
end -- mature

invariant
min_balance: balance >= minimum

end -- class INVEST

135
deferred class MENU

feature {ACCOUNTS}
menu is
-- show the menu
-- get and execute menu choices
do
show_choices
from get_choice
until end_chosen
loop
do_choice
get_choice
end
end -- menu

feature {NONE}
show_choices is
-- show the valid menu choices
do
io.putstring ("%N%TMenu choices%N%N")
io.putstring ("%TD%TDeposit money%N")
io.putstring ("%TW%TWithdraw money%N")
io.putstring ("%TB%TShow the balance%N")
io.putstring ("%TQ%TQuit the system%N")
io.putstring ("%TH%THelp: Show the menu choices%N")
end -- show_choices

get_choice is
-- get a valid menu choice from the user
do
from read_choice
until valid_choice
loop
io.putstring ("That is not a valid choice. Try again%N")
io.putstring ("The valid choices are D, W, B, Q, and H%N")
read_choice
end
end -- get_choice

read_choice is
-- read a menu choice from the user
do
io.putstring ("%NEnter menu choice: ")
io.readchar
io.next_line

136
end -- read_choice

valid_choice: BOOLEAN is
-- has the user entered a valid choice?
do
inspect io.lastchar.upper
when 'D', 'W', 'B', 'Q', 'H'
then Result := true
else Result := false
end
end -- valid_choice

end_chosen: BOOLEAN is
-- has the user chosen to finish?
do
Result := io.lastchar.upper = 'Q'
end -- end_chosen

do_choice is
-- execute the choice made by the user
do
inspect io.lastchar.upper
when 'D' then do_deposit
when 'W' then do_withdraw
when 'B'' then show_balance
when 'H' then show_choices
end -- inspect
end -- do_choice

do_deposit is
-- get the amount to deposit, then deposit it
local amount: REAL
do
io.putstring ("%TEnter the amount to deposit: ")
io.readreal
deposit (io.lastreal)
end -- do_deposit

do_withdraw is
-- get the amount to withdraw
-- if there is enough money, withdraw the amount
local amount: REAL
do
io.putstring ("%TEnter the amount to withdraw: ")
io.readreal
amount := io.lastreal

137
withdraw (amount)
end -- do_withdraw

withdraw (amount: REAL) is


-- withdraw this amount if there is enough money
deferred
end -- withdraw

deposit (amount: REAL) is


-- add the amount to the balance
deferred
end -- deposit

show_balance is
-- show the curent balance
deferred
end -- show_balance

end -- class MENU

138
deferred class INTERACCT

inherit
ACCOUNT
MENU

feature {ACCOUNTS}
deposit (amount: REAL) is
-- add amount to balance
do
balance := balance + amount
end -- deposit

enough (amount: REAL): BOOLEAN is


-- does the account contain this amount?
do
Result := balance >= amount
end -- enough

withdraw (amount: REAL) is


-- subtract this amount from the balance
do
balance := balance - amount
end -- withdraw

end -- class INTERACCT

139
class SAVINGS

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

creation {ACCOUNTS}
make

feature {NONE}
set_id is
-- set the account id to 'S'
do id := 'S' end -- set_id

set_rate is
-- set the interest rate for savings account
do rate := 4.5 end -- set_rate

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

end -- class SAVINGS

140
class CHEQUE

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

creation {ACCOUNTS}
make, make_zero

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

set_id is
-- set the account id to 'C'
do id := 'C' end -- set_id

make_zero is
-- create the account with zero balance
do
set_id
end -- make_zero

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

penalise is
-- tell the user the transaction failed
-- apply the penalty for bouncing a check (balance cannot go negative)
do
io.putstring ("%NInsufficient funds")
if balance >= penalty
then balance := balance - penalty
else balance := 0

141
end
end -- penalise

end -- class CHEQUE

142

Das könnte Ihnen auch gefallen