Java Arena
Step 3 of 4

A program built on polymorphism

Steps 1 and 2 looked at the types an object has, what a variable of each type lets you call, and how to check for a type with instanceof and cast to it. This step builds a small program around the idea that makes polymorphism worth having: code that works with one general type, and objects that each do their own thing.

A payroll

A company pays its staff every month, but not all in the same way. A salaried employee gets a twelfth of a yearly salary. An hourly employee gets an hourly rate times the hours logged. Employee is an abstract class that knows the name and leaves the pay to each kind of employee, and Payroll keeps them all in one list:

Main.java
public class Main {
    public static void main(String[] args) {
        Payroll payroll = new Payroll();
        payroll.add(new SalariedEmployee("Rosa", 54000));

        HourlyEmployee sam = new HourlyEmployee("Sam", 22);
        sam.logHours(80);
        sam.logHours(35);
        payroll.add(sam);

        payroll.add(new SalariedEmployee("Omar", 61200));
        payroll.printSlips();
    }
}
Employee.java
public abstract class Employee {
    private String name;

    public Employee(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public abstract int monthlyPay();

    @Override
    public String toString() {
        return this.name + ": " + this.monthlyPay();
    }
}
SalariedEmployee.java
public class SalariedEmployee extends Employee {
    private int yearlySalary;

    public SalariedEmployee(String name, int yearlySalary) {
        super(name);
        this.yearlySalary = yearlySalary;
    }

    @Override
    public int monthlyPay() {
        return this.yearlySalary / 12;
    }
}
HourlyEmployee.java
public class HourlyEmployee extends Employee {
    private int hourlyRate;
    private int hours;

    public HourlyEmployee(String name, int hourlyRate) {
        super(name);
        this.hourlyRate = hourlyRate;
        this.hours = 0;
    }

    public void logHours(int hours) {
        this.hours += hours;
    }

    @Override
    public int monthlyPay() {
        return this.hourlyRate * this.hours;
    }
}
Payroll.java
import java.util.ArrayList;

public class Payroll {
    private ArrayList<Employee> employees;

    public Payroll() {
        this.employees = new ArrayList<>();
    }

    public void add(Employee employee) {
        this.employees.add(employee);
    }

    public int total() {
        int total = 0;
        for (Employee employee : this.employees) {
            total += employee.monthlyPay();
        }
        return total;
    }

    public void printSlips() {
        for (Employee employee : this.employees) {
            System.out.println(employee);
        }
        System.out.println("Total: " + this.total());
    }
}
Output
Rosa: 4500
Sam: 2530
Omar: 5100
Total: 12130
  • Payroll knows only Employee. add takes an Employee, the list is an ArrayList<Employee>, and total calls monthlyPay() on each. It never asks which kind of employee it has.
  • Each kind brings its own monthlyPay(). Employee's toString calls this.monthlyPay(), and the object's own version runs, so each slip shows the right pay.
  • sam is an HourlyEmployee variable, because main needs logHours, which only hourly employees have. Handed to add, the same object is just an Employee to the payroll. Each part of the program uses the type that has what it needs, and no more.
  • monthlyPay() is abstract, since there's no pay for "an employee" in general. javac won't compile a class that extends Employee without one, unless that class is abstract too. A class further down, such as one that extends SalariedEmployee, can use the version it inherits.

A new kind of employee

The company takes on interns, who get a fixed 900 a month. That's one new class:

public class Intern extends Employee {
    public Intern(String name) {
        super(name);
    }

    @Override
    public int monthlyPay() {
        return 900;
    }
}

And that's the whole change. Payroll isn't touched: with payroll.add(new Intern("Kai")); in main, the slips get a line Kai: 900, and the total grows by 900. Payroll was written before interns existed, and it works with them, as it will with every class that extends Employee.

Without polymorphism

Compare a Payroll that works out the pay itself with instanceof (with getters for each kind's numbers):

public int total() {
    int total = 0;
    for (Employee employee : this.employees) {
        if (employee instanceof SalariedEmployee) {
            SalariedEmployee salaried = (SalariedEmployee) employee;
            total += salaried.getYearlySalary() / 12;
        } else if (employee instanceof HourlyEmployee) {
            HourlyEmployee hourly = (HourlyEmployee) employee;
            total += hourly.getHourlyRate() * hourly.getHours();
        }
    }
    return total;
}
  • The pay rules have moved out of the classes they belong to, into Payroll.
  • Interns need another else if, here and in every other method that works out pay. Forget one, and interns quietly get 0.
  • With a monthlyPay() in each class, each rule lives next to its own data, and a new kind of employee is one new class.

A list of an interface type

The general type can just as well be an interface. A notification service sends emails, text messages and app alerts. They have nothing in common but being sent, so they share an interface, and no superclass other than Object:

public interface Notification {
    String deliver();
}

An ArrayList<Notification> can hold all of them, and a loop can call deliver() on each, which returns the message as that kind sends it. The last task of this step builds that.

Your task

Challenge 1 of 3

The company also has salespeople. A salesperson gets 1800 a month, plus 5 percent of the month's sales, rounded down as integer division does. Employee.java, SalariedEmployee.java and Payroll.java are the lesson's. In SalesEmployee.java, write the class SalesEmployee, which extends Employee, with:

  • a constructor SalesEmployee(String name); a new salesperson has sold nothing yet
  • recordSale(int amount), which adds a sale to the month's sales
  • monthlyPay(), overridden to return 1800 plus 5 percent of the sales

Don't change Payroll: it already works with every kind of employee. The check runs:

Payroll payroll = new Payroll();
SalesEmployee lin = new SalesEmployee("Lin");
lin.recordSale(12000);
lin.recordSale(4500);
payroll.add(new SalariedEmployee("Rosa", 54000));
payroll.add(lin);
payroll.printSlips();

and expects:

Rosa: 4500
Lin: 2625
Total: 7125

Challenge 2

A manager is a salaried employee with 400 a month on top of the salary. Sam logs his hours after he's been added to the list. What does this program print?

Challenge 3

A notification service sends three kinds of message. Notification.java is the interface from the lesson, with one method, String deliver(), which returns the message as it's sent. Email.java and PushAlert.java are ready. Write:

  • In Sms.java, the class Sms, which implements Notification. Its constructor is Sms(String phone, String text), and deliver() returns SMS to , the phone number, : and the text. A text message has little room, so a text longer than 20 characters is cut to its first 20 characters, followed by ....
  • In Main.java, the method sendAll(ArrayList<Notification> outbox), which prints what each notification delivers, one per line, and then how many were sent, like 4 sent.

sendAll shouldn't care which kind each notification is. The check runs:

ArrayList<Notification> outbox = new ArrayList<>();
outbox.add(new Email("ada@example.com", "Your invoice"));
outbox.add(new Sms("555-0142", "Your code is 4417"));
outbox.add(new PushAlert("Weather", "Rain at 3 pm"));
outbox.add(new Sms("555-0199", "Table for 4 is ready at the front desk"));
sendAll(outbox);

and expects:

Email to ada@example.com: Your invoice
SMS to 555-0142: Your code is 4417
[Weather] Rain at 3 pm
SMS to 555-0199: Table for 4 is ready...
4 sent