Inheritance and Abstraction
The Aims
At present we have set up the AccountType as an enum.... this won't cut it in the real world, as checking accounts, savings accounts and credit cards are all somewhat different. They have some elements in common, such as a name and a balance. However the withdraw method will be quite different. Checking accounts will need to check the balance and/or an overdraft facility. Savings accounts will only allow a withdrawl if there is sufficient funds. Credit cards can allow withdrawls up to the amount of the credit limit. The addInterest method will only apply to Savings Accounts and Credit Cards, and will have different rates.
Finally, if you have time, you will make the Account class abstract, so it can no longer be instantiated.
Part 1: Defining the Subclasses
We will firstly define three subclasses of our class called Account.
- Remove the AccountType and InterestRate from the Account class, and adjust all the code so that it compiles.
Click here to see the sample solution
public class Account {
private double balance;
private String name;
public Account() {
this.balance = 50;
this.name = "John";
}
public Account(double balance, String name) throws DodgyNameException {
if (name.equals("Fingers")) {
throw new DodgyNameException();
}
this.balance = balance;
this.name = name;
}
public boolean withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
System.out.println(name + " now has a balance of " + balance);
return true;
} else {
System.out.println(name + " has insufficient funds for this withdrawal");
return false;
}
}
public boolean withdraw() {
return withdraw(20);
}
public void setName(String name) throws DodgyNameException {
if (name.equals("Fingers")) {
throw new DodgyNameException();
}
this.name = name;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public String getName() {
return name;
}
public void addInterest() {
balance *= 1.1;
}
@Override
public String toString() {
return "Account [balance=" + balance + ", name=" + name + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(balance);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Account other = (Account) obj;
if (Double.doubleToLongBits(balance) != Double.doubleToLongBits(other.balance))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
-
Define a new class called SavingsAccount that extends Account.
-
Define a constructor that takes two arguments for name and balance. Why does this need to be here if there is one already defined in the superclass?
-
In the constructor you have defined, pass the two parameters to the superclass constructor using the super keyword.
-
Define two further classes called CheckingAccount and CreditCard, and repeat steps 2 and 3 above.
Click here to see the sample solution
public class SavingsAccount extends Account {
public SavingsAccount(double balance, String name) throws DodgyNameException {
super(balance, name);
}
}
public class CheckingAccount extends Account {
public CheckingAccount(double balance, String name) throws DodgyNameException {
super(balance, name);
}
}
public class CreditCard extends Account{
public CreditCard(double balance, String name) throws DodgyNameException {
super(balance, name);
}
}
- Now empty the contents of the addInterest method in the Account class. If you have time you will make it abstract later on. It should now look like this;
public void addInterest() {
}
- Now, in your Savings and Credit card subclasses, override the addInterest method. Use the
@Overrideannotation to act as a check to make sure you overrode it correctly. Multiply the balance by 1.08 in the savings account and multiply the balance by 1.21 in the credit card account. Remember, the balance property is private in the superclass, so can only be accessed directly by the superclass. This means that in your addInterest methods, you will have to use get and set methods to access the balance variable.
Click here to see the sample solution
public class SavingsAccount extends Account {
public SavingsAccount(double balance, String name) throws DodgyNameException {
super(balance, name);
}
@Override
public void addInterest() {
setBalance(getBalance() * 1.08);
}
}
public class CreditCard extends Account{
public CreditCard(double balance, String name) throws DodgyNameException {
super(balance, name);
}
@Override
public void addInterest() {
setBalance(getBalance() * 1.21);
}
}
Part 2: Instantiating our classes
-
Define a new class with a main method called TestInheritance.
-
Within the main method declare an ArrayList called accounts of type Account, and initialise it with 4 elements. One should be an Account object, and then one for each of our subclasses. You do not need a loop for this bit. Make the main method throw any exceptions that could occur.
The names and balances can be whatever you like.
How can we get away with placing one of each of these types in the same arrayist? – Polymorphism!
-
Now loop through the arraylist, and call addInterest on each of the elements. Which addInterest will be called? There are three in total. One in each of the subclasses, and one in Account.
-
To prove that polymorphism has worked, within the loop display the names and balances. They should have changed by the appropriate amount.
Click here to see the sample solution
import java.util.ArrayList;
import java.util.List;
public class TestInheritance {
public static void main(String[] args) throws DodgyNameException {
List<Account> accounts = new ArrayList<>();
accounts.add(new Account(200, "Noah"));
accounts.add(new CheckingAccount(416, "Jimmy"));
accounts.add(new SavingsAccount(3000, "Diana"));
accounts.add(new CreditCard(-656, "Diana"));
for (int i = 0; i < accounts.size(); i++) {
accounts.get(i).addInterest();
System.out.println(accounts.get(i));
}
}
}
Part 3: Abstract classes
Currently it is possible to instantiate Account, even though it is an abstract concept like mammal is in the animal world. We will finally make it impossible to instantiate Account, but maintain our inheritance hierarchy. We will make the Account class abstract.
-
Modify the Account class declaration to include the abstract keyword.
-
Modify the addInterest method and make it abstract. Don't forget the semi-colon.
-
Modify the code inside the main method so to remove the code that creates an Account.
-
Implement the addInterest method in the CheckingAccount class - it does not need to do anything.
-
Run your code. It should work as before. You have an array where the array type is abstract, and it contains objects of the various subclass types.
Click here to see the sample solution
public abstract class Account {
...
public abstract void addInterest();
...
}
public class CheckingAccount extends Account {
public CheckingAccount(double balance, String name) throws DodgyNameException {
super(balance, name);
}
@Override
public void addInterest() {
}
}
import java.util.ArrayList;
import java.util.List;
public class TestInheritance {
public static void main(String[] args) throws DodgyNameException {
List<Account> accounts = new ArrayList<>();
accounts.add(new CheckingAccount(416, "Jimmy"));
accounts.add(new SavingsAccount(3000, "Diana"));
accounts.add(new CreditCard(-656, "Diana"));
for (int i = 0; i < accounts.size(); i++) {
accounts.get(i).addInterest();
System.out.println(accounts.get(i));
}
}
}