CS 2102 - Dterm 10

Homework 4 - Mutating Object State

Due: Friday, April 9 at 5pm

Acknowledgements

copyright 2009 Felleisen, Proulx, et. al. (Modified by Glynis Hamel)

Assignment Goals

Assignment

A bank keeps a list of different kinds of bank accounts: checking accounts, savings accounts, and line of credit accounts. Customers make deposits into and withdrawals from these accounts. Checking accounts require that the customer keeps a minimum balance below which withdrawals may not be made. For line of credit accounts, the balance represents the amount of money owed. Credit accounts must also record the maximum amount that the customer can borrow. Customers can withdraw money from line of credit accounts, as long as the amount being withdrawn does not increase the balance owed above the maximum amount. When a customer deposits money to a credit line account, it decreases the amount owed by the deposited amount. Customers are not allowed to overpay the amount owed in the credit line accounts.

A word about templates...

You may have noticed that Eclipse provides an Outline window that shows the fields of a class and their data types, and the methods defined for a class and their data types - the same information we've been providing in our templates. From this point on you are not required to provide templates; use the informationin the Outline window to help in the design of your methods (Window -> Show View -> Outline).

Problems

  1. Download the zip file containing the preliminary java source files for Homework 4. You can import the file as an existing project.

    In the Examples.java file, make several examples of data for Credit accounts.

  2. Design the method deposit for the abstract class Account:
    // EFFECT: Add the given amount to this account
    // Returns the new balance
    public int deposit(int amount)
    
    Note that the method does two things: it mutates the account, and also returns a value.

    You need to determine if the method should be defined as an abstract method, if it should be implemented in the abstract class, if it should be overridden in the subclasses, etc. If a transaction cannot be completed, the method should throw a RuntimeException such as:

    
    throw new RuntimeException("Balance too low: " + this.balance);
    
    (When + is used with String objects it concatenates the Strings.) Make the message meaningful. You may add to the message information about the account that caused the problem - the customer name, or the current balance available, or how much more there would need to be in the account for the transaction to go through.

    Testing Exceptions

    We need to be able to test that methods throw the exceptions we expect them to throw. Suppose bobAcct is a Checking account, and the method invocation:
       this.bobAcct.withdraw(1000) 
    
    results in a RuntimeException with the message:
       "1000 is not available". 
    
    The test would then be:
    
    t.checkException(
      "Testing withdrawal from checking",  
      new RuntimeException("1000 is not available"), 
      this.bobAcct,
      "withdraw",
      1000);
    
    The first argument is a String that describes what we are testing it is optional and can be omitted. The second argument defines the Exception our method invocation should throw. The third argument is the instance that invokes the method, the fourth argument is the method name, and after that we list as many arguments as the method consumes all separated by commas. It could be no arguments, or five arguments it does not matter. For our method that performs the withdrawal, it will just be the amount we are trying to withdraw.

    Every time you define a method that throws an exception, provide a test for that exception in the testExceptions() method of the Examples class.

    Testing Methods that have Side Effects

    For transactions that do go through without throwing an exception, you should develop a set of test cases. Because the method deposit() produces a value as well as has an effect on the state of the object that invoked the method, you must test both parts.

    The Examples.java file provides instances of data that can be used in our tests for checking accounts. It also defines a method reset() that initializes the values for the data we expect to work with and which may change during the tests. We can then design the tests as follows (assuming that this.check1 is the instance that invokes the method that is being tested):

    
    //Tests the deposit method inside certain accounts.
    void testDeposit(Tester t){
      reset();
    
      t.checkExpect(check1.deposit(100), 100);
      t.checkExpect(check1, 
        new Checking(1, 100, "First Checking Account", 0));
    
      // you need to add additional tests here...
    
      reset();
    }
    
    Notice that we use the reset() method twice. At the start, we make sure that the data we use have the correct values before the method is invoked, and after the test we reset the data back to its original values, so that the tests that were run don't affect any other part of the program. Notice the tests themselves - there are two kinds. The first one is just like what we have done in the past - we compare the value produced by the method invocation with the expected value. The second test verifies that the state of the object we were modifying did indeed change as expected. Convince yourself that both kinds of tests are needed. (Look at the following incorrect implementations of deposit() in the Checking class to see why both kinds of tests are necessary):
    
    //EFFECT: Add the given amount to this account
    //Return the new balance
    public int deposit(int amount){
      return this.balance + amount;
    }
    
    
    //EFFECT: Add the given amount to this account
    //Return the new balance
    public int deposit(int amount){
      this.balance = balance + amount;
      return amount;
    }
    
    
    Of course, you should test deposits into all three kinds of accounts.

  3. Design the method withdraw() for the abstract class Account:
    // EFFECT: Withdraw the given funds from this account
    // Return the new balance
    public int withdraw(int funds)
    
    Provide a set of test cases.

  4. The class Bank keeps track of all accounts. Design the method openAcct() for the class Bank that allows a customer to open a new account in the bank.
    // EFFECT: adds a new account to the list of accounts kept by this bank
    public void openAcct(Account acct)
    
    Make sure you design your tests carefully.

  5. Design the method deposit() for class Bank that deposits the given amount to the account with the given name and account number. deposit() is a void method. Make sure you report any problems, such as no such account, or that the transaction cannot be completed. Make sure you design your tests carefully.

More on Equality

In the following problem we'll investigate another way of testing equality. This material is covered in pages 321 - 330 in the textbook.
  1. In this problem you will define a method that will determine whether this account is the same as the given account. For the purposes of this problem, we will consider two accounts to be the same only if all of their fields are the same (it's not enough just to compare the account numbers, for example).

    The design of the method same is similar to the technique described in the textbook for the classes Coffee and Decaf. The difference here is that the superclass is abstract, so you should take advantage of abstracting similarities to the abstract class Account where you can.

    Begin by designing the method same for the abstract class Account.

    Now design the same method in one of the concrete account classes (for example the Checking class; let's refer to it by the name sameChecking).

    You will need a helper method that determines whether the given account is a Checking account. So, design the method isChecking that determines whether this account is a checking account. You need to design this method for the whole class hierarchy - the abstract class Account and all subclasses. Do the same to define the methods isSavings and isCredit. We're almost done. We need another helper method that tells Java that our account is of the specific type. In the class Checking the method will look like this:

      // produce a checking account from this account
      Checking toChecking(){
        return this; }
    
    Of course, we cannot convert other accounts into checking accounts, and so in the other concrete classes the method should throw a RuntimeException with the appropriate message. We need the same kind of method for every class that extends the Account class.

    Finally, we can define the body of the same method in the class Checking:

    
    // is this checking account the same as that account?
    boolean same(Account that){
      if (that.isChecking())
        return that.toChecking().sameChecking(this);
      else 
        return false;
    }
    
    That means, we still need the method sameChecking in Checking, but it only needs to be defined within the Checking class and can thus be defined with private visibility. Finish this - with appropriate test cases. Finish designing the same method for the other two account classes.

Note

Some of you who have taken an intro course in Java may be familiar with another way of checking for equality using two Java language features, the instanceof operator and casting. Using these features, you could write same() this way:
// is this checking account the same as that account?
boolean same(Account that){
  if (that instanceof Checking)
    return ((Checking)that).sameChecking(this);
  else 
    return false;  
}
However, this version is problematic and not safe, once subclasses are considered. For further information, refer to "Pitfall #4" in the previously-referenced article on the difficulties in overriding equals().

What to Turn In

Create an archive of your Eclipse project. Using web-based turnin, turn in a single zip file containing all code and documentation for this assignment. Follow the naming conventions when naming your file.