Continuing our work with class design, this lab explores some more advanced class design concepts.
Oracle Java Tutorial links
- Identify when and how to apply abstract classes.
- Construct abstract Java classes and subclasses.
- Use the static and final keywords.
- Create top-level and nested classes.
- Use enumerated types.
If you have not already done so in the lab on Java Class Design, do the exercises for Classes in the Oracle Java tutorial here. Once you are done, the answers can be checked here.
Do the exercises for Variables in the Oracle Java tutorial here. Once you are done, the answers can be checked here.
Do the exercises for Nested Classes in the Oracle Java tutorial here. Once you are done, the answers can be checked here.
Do the exercises for Enum Types in the Oracle Java tutorial here. Once you are done, the answers can be checked here.
The concepts described above allow elegant and efficient class designs to be created. To illustrate this, we are going to create the data structures of a bank application. (The rest of the bank application is left as an exercise for the reader). We will build classes to support multiple types of bank accounts and multiple types of transactions on those accounts.
You will be asked to create specific classes implementing specific interfaces. However, there are many ways of structuring this implementation to pass the unit tests given below. You are looking to create an elegant implementation, with the least repetition of code, and which clearly represents the design asked for. There is no one right answer. Make use of classes, subclasses, interfaces and abstract classes and methods as you see fit.
To begin, create the interface Account in the package design. The interface will have the following method declarations:
public interface Account { public String getAccountHolder(); public String getAccountNumber(); /** * The current balance of the account, calculated from the transactions * applied to the account * * @return balance of account */ public BigDecimal getAccountBalance(); /** * Add transaction to the account. * * @param transaction * @throws RuntimeException * if transaction does not use this account or transaction is * not valid for this type of account. */ public void addTransaction(Transaction transaction); /** * Return a string containing all the transactions of this account * * @return transaction output */ public String printTransactions(); /** * Return the account type, the account number, the account holder, and * balance on this account * * @return account output */ @Override public String toString(); }
We will be using java.math.BigDecimal to represent currency amounts. Using floating point numbers with money is a bad idea.
Some accounts can be joint accounts (i.e. there are two account holders). Create another interface JointAccount which extends interface Account.
public interface JointAccount extends Account { public String getSecondAccountHolder(); /** * Return the account type, the account number, the account holders, and all * transactions on this account * * @return account output */ @Override public String toString(); }
Next create the interface Transaction in the same package. Note that this interface defines an enum for the type of transaction.
public interface Transaction { public static enum TransactionType { DEPOSIT, WITHDRAWAL, TRANSFER, INTEREST_PAYMENT, } public TransactionType getType(); public Date getDate(); public BigDecimal getAmount(); public Account getTargetAccount(); }
Now it gets interesting. Create an implementation of Transaction for each of the four transaction types, so create classes Deposit, Withdrawal, Transfer and InterestPayment, again in the same package. The constructors of these classes should set the necessary fields, for example:
public InterestPayment(Account targetAccount, Date date, BigDecimal amount) { ... } //(and similar for Deposit and Withdrawal)
Transfer is a special case in that money is transferred from a source account to a target account, so this class needs a different constructor and an extra method:
public Transfer(Account sourceAccount, Account targetAccount, Date date, BigDecimal amount) { ... } public Account getSourceAccount() { ... }
Next, create the various types of accounts as classes implementing Account or JointAccount. There can be current accounts and savings accounts. Create the classes CurrentAccount and SavingsAccount implementing Account, and JointCurrentAccount and JointSavingsAccount implementing JointAccount.
Now there are some constraints on the types of transactions allowed by each account type, which must be checked by the addTransaction method of the accounts:
- Each transaction can only be added to an account which is the target (or source) account for that transaction.
- Savings accounts may only accept interest payment or transfer transactions (not deposits or withdrawals).
- Current accounts may accept all transactions apart from interest payments.
Finally, implement the remaining methods as described by the interfaces above. Sample Account.toString output is as follows:
Current account 0001 : Bob : £5.00 Savings account 0002 : Fred : £3.14 Joint current account 0003 : Bob, Fred : £505.00 Joint savings account 0004 : Fred, Alice : £500.00
Sample Account.printTransactions output is as follows:
16/01/2016 : DEPOSIT : £20.50 16/01/2016 : WITHDRAWAL : £10.50 16/01/2016 : TRANSFER (from account 0001 to account 0003) : £5.00 16/01/2016 : INTEREST_PAYMENT : £3.14
Automated tests have been created for this exercise: AccountTest.java, TransactionTest.java.