/** The BankAccount class implements a simple bank account. @author Ben Dugan @version 11/11/2001 */ public class BankAccount { private int number; private double balance; private String name; /** Create a new BankAccount object, with the given initial values. @param acctNumber an account number to use @param initBalance an initial balance @param acctName the name of the account owner */ public BankAccount(int acctNumber, double initBalance, String acctName) { this.number = acctNumber; this.balance = initBalance; this.name = acctName; } /** Increase the balance by the given amount. @param amount the amount to deposit */ public void deposit(double amount) { this.balance = this.balance + amount; } /** Answer the current balance. @return the current balance */ public double getBalance() { return this.balance; } /** Answer the account number. @return the account number */ public int getAccountNumber() { return number; } /** Decrease the balance by the given amount. @param amount the amount to deposit @return true if and only if there were sufficient funds for the withdrawal */ public boolean withdraw(double amount) { if (amount <= this.balance) { this.balance = this.balance - amount; return true; } else { return false; } } /** Transfer money between accounts. @param other a BankAccount to tranfer from @param amount an amount to transfer */ public void transferFrom(BankAccount other, double amount) { other.withdraw(amount); this.deposit(amount); } }What about the collection of BankAccounts in the bank? Let's call this kind of object
Accounts
. What kind of object is it? The
Accounts object is responsible for maintaining a collection of
accounts for the bank. Is the collection ordered? Even though we
probably access an account via its account number, this does not mean
that there is an inherent order to the accounts. An account number is
just a unique key used to access a single account. Whereas
there may be two people named "Bill Wattie", they ought to have two
separate bank accounts, each with their own unique account number.
Let's use a HashMap that associates account numbers with BankAccounts
as the fundamental representational component of our Accounts class:
public class Accounts { private HashMap accounts; /** Create a new, empty Accounts collection. */ public Accounts() { this.accounts = new HashMap(); } /** Add an account to the collection. */ public void addAccount(BankAccount account) { int accountNumber = account.getAccountNumber(); Integer key = new Integer(accountNumber); accounts.put(key, account); } /** Retrieve an account from the collection. */ public BankAccount getAccount(int accountNumber) { Integer key = new Integer(accountNumber); return accounts.get(key); } }First, let's emphasize that this class looks a lot like the MusicCollection class. Its main job is to maintain a collection of things and provide a nice interface for adding and getting access to those things. There are some strange differences though. Can you spot them?
getAccount
and
addAccount
methods. Both methods create an Integer object
to use as a key for accessing the HashMap. The reason we do this is
related to the discussion of casting in a prior lesson. Recall the
interface for HashMaps:
public class HashMap { /** Answer the value associated with the given key. */ public Object get(Object key); /** Associate the given key with the given value. */ public void put(Object key, Object value); /** Remove the key and its corresponding value from the table. @return the value if it exists, null otherwise */ public Object remove(Object key); }This interface tells us that HashMaps can associate any key Object with a value Object. Recall that the primitive types int, char, and double - unlike most other classes/types in Java - do not have the "is-a" relationship with the Object class. This exception is a famous "wart" in the definition of the Java language. The reason for this exception is related to efficiency considerations that worried the original implementors of the Java language. We want to implement a method like
getAccount
as follows:
public BankAccount getAccount(int accountNumber) { return accounts.get(accountNumber); }However, if we do this, the compiler will refuse to compile our program. Why? Because the type of
accountNumber
in the
above example is int
and the type int
does
not have an "is-a" relationship to the type expected by the
get
method on HashMaps, which is Object. To deal with
this situation, the Java language provides us a set of classes whose
only job it is to "wrap" primitive types such as char, int, and
double. These classes are called Character, Integer, and Double,
respectively. Once we wrap a primitive thing like an int in an
Integer object, that object can be used in places where Objects are
expected. Here's a fragment of the Integer class interface
(interfaces to Character and Double are similar):
public class Integer { /** Create a new Integer containing the given value. */ public Integer(int value); /** Answer the integer value of this object. */ public int intValue(); }In our Accounts class, we use Integer objects to wrap the account number value before we associate new BankAccounts with their account numbers, and before we look up a BankAccount by its number.
As usual, we have several options for assigning these responsibilities. The first and perhaps most obvious option is to assign them to the Accounts object. We could, for instance, add a method to the Accounts class that prompts the user for an account number, then looks up that account, and prints the result to the screen. The method might look like this:
public void printAccount(Input in, Output out) { int number = in.readInt("Enter account number: "); BankAccount account = this.getAccount(number); out.println(account); }This turns out to not be a very good idea. Why? Because it violates a number of important principles of software design.
First, it violates what we might call the "Hedgehog Principle". The
name "Hedgehog Principle" comes from the saying "The fox knows many
things, but the hedgehog knows one great thing." The hedgehog is,
above all, an expert at rolling itself into a ball to defend itself.
The fox, for all of its general cunning cannnot defeat the expert
hedgehog. Taken to a programming context, this principle states that
classes should prefer to be excellent at doing one thing, rather than
be pretty good at a variety of things. By adding a method like
printAccount
to the Account class, we're asking it to be
good at both doing input and output, as well as managing a collection
of BankAccounts.
Second, the above design does not typically result in maintainable software. Imagine trying to ship the above software to a country where another language is spoken. We've hardcoded the English language prompt into the definition of the Accounts class. To ship the software to Germany, for instance, we'd have to make a copy of the class (call it something like GermanAccounts), and then go change the prompts in the GermanAccounts class. While this approach would work, what happens when we discover a bug in the original Accounts class? We'd have to fix the bug in the Accounts class, as well as GermanAccounts, FrenchAccounts, and so on. Doing this in practice would be a maintinence nightmare.
Finally, it does not partition the work between different programmers very well. In a real system, getting the user interface right is a big job, and we'd like to allow some programmers to focus just on building an excellent interface, while other programmers might focus on building the underlying system objects, such as BankAccount and Accounts.
These issues encourage us to create a separate class whose job it was only to be an expert at performing input and output. We'll call this class BankUI (short for BankUserInterface). It will have methods that support the following typical bank teller activities:
public class BankUI { private Accounts accounts; private Input input; private Output output; private nextAccountNumber; /** Create a new BankUI for the given objects. */ public BankUI(Accounts accounts, Input in, Output out) { this.accounts = accounts; this.input = in; this.output = out; this.nextAccountNumber = 0; } /** Create a new account */ public void createAccount() { int number = nextAccountNumber; nextAccountNumber = nextAccountNumber + 1; double balance = input.readDouble("Enter starting balance: "); String name = input.readString("Enter account name: "); BankAccount account = new BankAccount(number, balance, name); accounts.addAccount(account); output.println("Account# " + number + " created for " + name); } /** Get the balance from an account. */ public void checkBalance() { int number = input.readInt("Enter account number: "); BankAccount account = accounts.getAccount(number); output.println("The balance is " + account.getBalance()); } /** Deposit money into an account. */ public void depositMoney() { int number = input.readInt("Enter account number: "); BankAccount account = accounts.getAccount(number); double amount = input.readDouble("Enter amount: "); account.deposit(amount); output.println("New balance: " + account.getBalance()); } /** Withdraw money from an account. */ public void withdrawMoney() { int number = input.readInt("Enter account number: "); BankAccount account = accounts.getAccount(number); double amount = input.readDouble("Enter amount: "); boolean success = account.withdraw(amount); if (success) { output.println("Withdrawal succeeded."); } else { output.println("Withdrawal failed. Insufficient funds."); } } }
prompt> Input in = new Input(); prompt> Output out = new Output(); prompt> Accounts accounts = new Accounts(); prompt> BankUI ui = new BankUI(accounts, in, out); prompt> ui.createAccount(); Enter starting balance: 200.50 Enter account name: Ahab Account# 0 created for Ahab prompt> ui.depositMoney(); Enter account number: 0 Enter amount: 50.0 New balance: 250.50 prompt>Notice that the first four lines are setup code. We need to create our basic system objects, and then we stitch them together with a BankUI object. After that, we send messages to the BankUI object and it handles the input and output required to access and update BankAccount objects.
In real life, we wouldn't expect the teller to actually type Java code to send messages to the BankUI. We can actually internalize the calling of the BankUI methods in the class itself:
public class BankUI { // All the other methods... /** Print a menu of options. */ public void printMenu() { output.println("Please enter one of the following options:"); output.println(" [n]ew account"); output.println(" [d]eposit"); output.println(" [w]ithdraw"); output.println(" [q]uit"); } /** Prompt teller and dispatch to appopriate method. @return false if and only if the teller quit */ public boolean dispatch() { char option = input.readChar("Enter Option: "); boolean quitting = false; if (option == 'n') { this.createAccount(); } else if (option == 'd') { this.depositMoney(); } else if (option == 'w') { this.withdrawMoney(); } else if (option == 'q') { output.println("Goodbye!"); quitting = true; } else { output.println("Invalid option."); } return quitting; } /** Run the UI until the teller decides to quit. */ public void go() { boolean quitting = false; while (!quitting) { printMenu(); quitting = dispatch(); } } }We've added three methods to our BankUI class. The first prints a menu of options that are available to the bank teller. The second consumes a character from the teller (by which he or she selects an option), and calls the appropriate method. The third implements a loop, that continually prints the menu and dispatches the chosen option to the right method. Let's use this new class in our interpreter:
prompt> Input in = new Input(); prompt> Output out = new Output(); prompt> Accounts accounts = new Accounts(); prompt> BankUI ui = new BankUI(accounts, in, out); prompt> ui.go(); Please enter one of the following options: [n]ew account [d]eposit [w]ithdraw [q]uit Enter option: n Enter starting balance: 200.50 Enter account name: Ahab Account# 0 created for Ahab Please enter one of the following options: [n]ew account [d]eposit [w]ithdraw [q]uit Enter option: d Enter account number: 0 Enter amount: 50.0 New balance: 250.50 Please enter one of the following options: [n]ew account [d]eposit [w]ithdraw [q]uit Enter option: x Invalid option. Please enter one of the following options: [n]ew account [d]eposit [w]ithdraw [q]uit Enter option: q Goodbye! prompt >Notice that the first four lines are identical to the original example -- we create and stitch together the system objects. After that, however, we make a single call to the
go
method on the
BankUI object, and this method executes a loop which handles the
interaction with the teller. In a way, the go
method
implements a simple interpreter that forever does the following three
things: reads an option from the teller, executes that option, and
then prints the result. This read-evaluate-print interpreter cycle is
another common pattern in many real-world systems.
While we have combined the jobs of the view and the controller in the BankUI object, we are still faithful to the basic distinctions of the MVC pattern. By decoupling the components in this manner, we gain a great deal in terms of flexibility and reusability. For instance, assume that we need to implement a fancier version of our user interface. In this day and age, text-driven user interfaces are not very popular anymore. Most user interfaces are graphical in nature, and rely heavily on mouse inputs and menus for option selection. Given the above design, we can reuse the BankAccount and Accounts classes without modification, and simply implement a new class (perhaps called GraphicalBankUI) to manage the appropriate display objects and handle mouse inputs from the user. Furthermore, we can mix and match our user interface components in an interesting manner. We can, for instance, start our development by building a simple text-driven user interface, and then later start working on a graphical user interface. During the development and testing of this more complicated interface, we can still rely on the old text-driven interface to test-drive our system, or provide access to features that are not yet available in the nascent graphical user interface.