Вы находитесь на странице: 1из 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: Specification. Analysis. Design. Charts: one or more of client, inheritance, and class diagrams. Eiffel code that is new or changed in that section.

1. 2. 3. 4. 5.

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: Specification. Ace. Eiffel text files. Eiffel executable file, named bank. A sample run of the system.

1. 2. 3. 4. 5.

CASE STUDY PART 1: LOOK AND FEEL


1.1 1.2 1.3 1.4 1.5 1.6 Specification Analysis Solution design Client chart Ace file Solution code

1 6
6 6 6 7 7 8

PART 2: DATA FLOW


2.1 2.2 2.3 2.4 Specification Analysis Solution design Solution code

9
9 9 9 9

PART 3: ROUTINES
3.1 3.2 3.3 3.4 3.5 Specification Analysis Solution design Solution code Common error

11
11 11 11 11 14

PART 4: OBJECTS
4.1 4.2 4.3 4.4 4.5 Specification Analysis Design Client chart Solution code

15
15 15 15 15 16

4.6

Common errors

22

PART 5: BEHAVIOUR
5.1 Specification

23
23 23 23 23 23 24 24 24 29

5.2 Analysis 5.2.1 Creation status 5.2.2 Export policies 5.2.3 Assertions 5.3 5.4 5.5 5.6 Design Client chart and class diagrams Solution code Common errors

PART 6: SELECTION
6.1 6.2 6.3 6.4 6.5 Specification Analysis Design Solution code Common errors

30
30 30 30 32 35

PART 7: ITERATION
7.1 7.2 7.3 7.4 7.4 7.5 Specification Analysis Design Charts Solution code Common errors

36
36 36 36 37 39 47

PART 8: ARRAYS
8.1 8.2 8.3 Specification Analysis Design

48
48 48 48

8.4 8.5

Charts Solution code

48 48

PART 9: LISTS
9.1 9.2 9.3 9.4 9.5 Specification Analysis Design Charts Solution code

53
53 53 53 54 54

PART 10: INHERITANCE


10.1 10.2 10.3 10.4 10.5 Specification Analysis Design Charts Solution code

62
62 62 62 62 62

PART 11: POLYMORPHISM


11.1 11.2 Specification Analysis

68
68 68 68 69 70 71 72 72 74 74 75 76 76

11.3 Types of account 11.3.1 Focus: account balance 11.3.2 Focus: account id 11.3.3 Focus: interest rate 11.3.4 Focus: an interactive account 11.3.5 Focus: withdraw 11.4 11.5 11.6 11.7 11.8 Storing the accounts Inheritance chart Client chart Class diagrams Solution code

PART 12: COMPLEX INHERITANCE

97

12.1 12.2 12.3 12.4 12.5

Specification Analysis Design: list storage and retrieval Design: an inherited MENU Solution code

97 97 97 97 98

PART 13: CONSTRAINED GENERICITY


13.1 13.2 13.3 13.4 13.5 13.6 Specification Analysis Design: a keyed list Charts Design: a keyed, storable list Solution code

104
104 104 104 104 104 105

PART 14: THE COMPLETE BANK SYSTEM


14.1 14.2 14.3 14.4 14.5 Specification Inheritance charts Client charts Class diagrams Class listings

110
110 111 112 113 115

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

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

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

Part 2: Data flow


2.1 Specification
Execute the following sequence of actions on a bank account: 1. 2. 3. Read in and show the balance of a bank account. Deposit an amount, withdraw an amount, and show the balance. Read in the amounts from the user. 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.

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 patron: CUSTOMER make

CUSTOMER name: STRING gender: CHARACTER address: STRING account: ACCOUNT make show use

ACCOUNT balance: REAL interest_rate: REAL is 4.5 make show deposit (amount: REAL) withdraw (amount: REAL) 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 W, w B, b Q, q H, h Deposit Withdraw up to the total amount in the account Show the balance Quit the system 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 richie: CUSTOMER make

CUSTOMER name, address: STRING gender: CHARACTER password: PASSWORD account: ACCOUNT menu: MENU make display login add_interest

PASSWORD password: STRING max_tries: INTEGER is 3 make login valid

MENU account: ACCOUNT make (acc: ACCOUNT)

ACCOUNT balance: REAL interest_rate: REAL is 4.5 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.
ARRAY [T]

BANK

CUST

PASSWORD

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.

BANK

LINKED _LIST [T]

CUST

TELLER

LINKED _LIST [T]

CUST

ATM

LINKED _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 patrons: L_L [CUSTOMER] teller: TELLER atm: ATM make

TELLER patrons: L_L [CUSTOMER]

ATM patrons: L_L [CUSTOMER] end_atm: INTEGER is 666 end_system: INTEGER is 999 make run

make 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 name, address: STRING gender: CHARACTER CUSTOMER password: PASSWORD account: ACCOUNT menu: MENU make display add_interest match (id: I): B login

PERSON

make display CUSTOMER

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

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 INVEST period 3 rates make new_day mature display display interest INTERACCT make deposit withdraw * balance display

SAVING rate = 4.5 withdraw

CHEQUE rate = 0.0 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 if enough (money) then withdraw (money) CHEQUE if enough (money + charge) 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

INVEST

INTEREST

INTERACCT

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

BANK

LINKED _LIST [T]

CUST

TELLER

LINKED _LIST [T]

CUST

ATM

LINKED _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 id: INTEGER password: PASSWORD accounts: ACCOUNTS make show match (id: I): BOOLEAN login
ACCOUNTS savings: SAVINGS cheque: CHEQUE invest: INVEST make show use end_day

ACCOUNT id: INTEGER balance: REAL

INTEREST rate: REAL

INVEST minimum: REAL i s 1000.0 period: INTEGER days: INTEGER make show new_day mature: BOOLEAN

make show

make add_interest

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 penalty: REAL is 5.00 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

-- charge for good transaction -- penalty for bouncing a check

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_ LIST

STORABLE

BANK

STORE _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

INTEREST

INTERACCT

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_ LIST KEYED

KEY_ LIST

CUST

BANK

KEY_LIST [T]

CUSTOMER

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

STORABLE

LINKED_ LIST

KEYED

PERSON

BANK

KEY_LIST

CUSTOMER

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 W B Q H Deposit Withdraw up to the total amount in the account Show the balance Quit the system 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_ LIST

STORABLE

PERSON

BANK

KEY_LIST

CUSTOMER

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

ACCOUNT

MENU

INVEST

INTEREST

INTERACCT

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 [T]

CUSTOMER

TELLER

KEY_LIST [T]

CUSTOMER

ATM

KEY_LIST [T]

CUSTOMER

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

PASSWORD

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 patrons: KEY_LIST [CUSTOMER]

ATM patrons: KEY_LIST [CUSTOMER] end_atm: INTEGER is 666 end_system: INTEGER is 999 make run system_finished

make run

The next set of five diagrams shows the customer subsystem.


KEY_LIST [T -> KEYED] KEYED id: CHARACTER

read_id find (key: CHARACTER) found: BOOLEAN PERSON name: STRING gender: CHARACTER address: STRING make show

set_id match (key: C): BOOLEAN show_id CUSTOMER password: PASSWORD accounts: ACCOUNTS make login show PASSWORD password: STRING max_tries: INTEGER is 3 make login 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 id: INTEGER balance: REAL

INTEREST rate: REAL

INVEST minimum: REAL i s 1000.0 period: INTEGER days: INTEGER make show new_day mature: BOOLEAN

make show

make add_interest

The final set of four diagrams shows the ATM account classes.
ACCOUNTS savings: SAVINGS cheque: CHEQUE invest: INVEST make show use end_day

ACCOUNT id: INTEGER balance: REAL

INTEREST rate: REAL

INVEST minimum: REAL i s 1000.0 period: INTEGER days: INTEGER make show new_day mature: BOOLEAN

make show

make add_interest

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 SAVINGS CHEQUE INVEST

ATM

KEY_LIST

CUSTOMER

PASSWORD ACCOUNTS

Inheritance order KEYED PERSON ACCOUNT ACCOUNT ACCOUNT CUSTOMER

INTEREST MENU MENU

INVEST INTERACCT INTERACCT INTEREST CHEQUE SAVINGS

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 penalty: REAL is 5.00 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

-- charge for good transaction -- penalty for bouncing a check

141

end end -- penalise end -- class CHEQUE

142

Вам также может понравиться