Java Arena
Step 6 of 7

Adding a feature

Programs grow. A musician using the practice log would like to know the length of an average session, and a new user would like to see which commands there are. With the logic and the user interface apart, each part of a new feature has a clear place:

  • What the program must know or work out goes into the logic class: new instance variables, new methods.
  • The new command, and the sentence it prints, go into the user interface: one more else if.

For average, the log needs the number of sessions, which it doesn't keep yet. So it gets one more instance variable, counted up in add, and a new method that divides. For help, nothing needs working out, so it only changes the user interface. The lines marked // new, and the whole average method, are all that changed:

Main.java
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        PracticeLog log = new PracticeLog();
        UserInterface ui = new UserInterface(scanner, log);
        ui.start();
    }
}
PracticeLog.java
public class PracticeLog {
    private int totalMinutes;
    private int longestSession;
    private int sessions; // new

    public PracticeLog() {
        this.totalMinutes = 0;
        this.longestSession = 0;
        this.sessions = 0; // new
    }

    public void add(int minutes) {
        this.totalMinutes = this.totalMinutes + minutes;
        if (minutes > this.longestSession) {
            this.longestSession = minutes;
        }
        this.sessions++; // new
    }

    public int total() {
        return this.totalMinutes;
    }

    public int longest() {
        return this.longestSession;
    }

    public double average() { // new
        if (this.sessions == 0) {
            return 0;
        }
        return (double) this.totalMinutes / this.sessions;
    }
}
UserInterface.java
import java.util.Scanner;

public class UserInterface {
    private Scanner scanner;
    private PracticeLog log;

    public UserInterface(Scanner scanner, PracticeLog log) {
        this.scanner = scanner;
        this.log = log;
    }

    public void start() {
        while (true) {
            System.out.print("> ");
            String command = this.scanner.nextLine();
            if (command.equals("quit")) {
                System.out.println("Keep practicing!");
                break;
            }
            if (command.equals("log")) {
                int minutes = Integer.valueOf(this.scanner.nextLine());
                this.log.add(minutes);
                System.out.println("Logged " + minutes + " minutes");
            } else if (command.equals("total")) {
                System.out.println("Total: " + this.log.total() + " minutes");
            } else if (command.equals("longest")) {
                System.out.println("Longest: " + this.log.longest() + " minutes");
            } else if (command.equals("average")) { // new
                System.out.println("Average: " + this.log.average() + " minutes"); // new
            } else if (command.equals("help")) { // new
                System.out.println("Commands: log, total, longest, average, help, quit"); // new
            } else {
                System.out.println("Unknown command: " + command);
            }
        }
    }
}
Input
log
30
log
45
average
help
quit
Output
> Logged 30 minutes
> Logged 45 minutes
> Average: 37.5 minutes
> Commands: log, total, longest, average, help, quit
> Keep practicing!
  • average() returns a double, and the cast (double) makes the division keep its decimals, as module 4 showed. With no sessions yet, it returns 0 instead of dividing by 0.
  • average() returns a number, and that's all. The words Average: and minutes are the user interface's business, just like the words around the total.
  • help changed only the user interface. A feature that only changes what is shown, such as a new message or a list of the commands, stays in the user interface. Main didn't change at all.

Try the logic first

When a feature needs new logic, write the method first, and try it with a few calls before any command uses it: log.add(30); log.add(45); System.out.println(log.average()); should print 37.5. When it gives the right answers, the user interface only has to call it and print the result. If something is then wrong, you know where to look.

Your task

Challenge 1 of 3

A water log helps a person drink enough during the day, and the goal is 2000 ml. WaterLog and UserInterface know the commands drink, total and quit. Add a feature, the command goal:

  • In WaterLog.java, add the method mlToGoal(). It returns how many milliliters are still missing, or 0 when the goal is reached.
  • In UserInterface.java, add the command goal. It prints Goal reached! when mlToGoal() is 0, and otherwise the missing milliliters, like 750 ml to go.

For the input shown, the program prints:

> Total: 500 ml
> 1500 ml to go
> Total: 1250 ml
> 750 ml to go
> Total: 2100 ml
> Goal reached!
> Cheers!

Challenge 2

A model railway program builds a train behind its engine. Train and UserInterface know the commands attach, which is followed by a line with the kind of car to add at the end, detach, which takes off the last car, and quit. Add the command count: the next line is a kind of car, and the program prints how many cars of that kind the train has, like sleeper cars: 2. A car counts when its kind is spelled exactly the same, capitals included.

  • In Train.java, add the method countOf(String kind), which returns the number.
  • In UserInterface.java, add the command.

For the input shown, the program prints:

> [engine]-[seats]
> [engine]-[seats]-[sleeper]
> [engine]-[seats]-[sleeper]-[dining]
> [engine]-[seats]-[sleeper]-[dining]-[sleeper]
> sleeper cars: 2
> cargo cars: 0
> [engine]-[seats]-[sleeper]-[dining]
> sleeper cars: 1
> End of the line

Challenge 3

An elevator goes between the floors 0 and 9. Elevator and UserInterface know the commands go, which is followed by a line with a floor number, where and quit. Add the command traveled, which prints how many floors the elevator has moved in all, like Floors traveled: 8. Going from floor 5 to floor 2 is 3 floors, and going up counts just like going down.

The elevator doesn't remember that yet, so the feature needs a new instance variable in Elevator, updated whenever it moves, and a method getTraveled() that returns it. A trip to a floor that doesn't exist doesn't move the elevator. Then add the command to UserInterface. For the input shown, the program prints:

> Floor 5
> Floor 2
> Floor 2
> No floor 12
> Floors traveled: 8
> Floor 0
> Floors traveled: 10
> Out of service