The problem specification is unchanged.
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.
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.
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 class’s 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.
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.
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.
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
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.
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
ensure patron_exists: patron /= Void
end -- class BANK
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
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
-- 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
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 attribu