Java Arena
Step 4 of 7

A class for the user interface

The logic of the practice log is now in PracticeLog, but main still holds the whole user interface: the loop, the reading and the printing. The next move gives the user interface a class of its own too, called UserInterface. Its constructor takes the two things it works with, a Scanner and the logic object, and its method start() runs the command loop. What's left of main only puts the program together:

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();
    }
}
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 {
                System.out.println("Unknown command: " + command);
            }
        }
    }
}
PracticeLog.java
import java.util.ArrayList;

public class PracticeLog {
    private ArrayList<Integer> sessions;

    public PracticeLog() {
        this.sessions = new ArrayList<>();
    }

    public void add(int minutes) {
        this.sessions.add(minutes);
    }

    public int total() {
        int total = 0;
        for (int minutes : this.sessions) {
            total = total + minutes;
        }
        return total;
    }

    public int longest() {
        int longest = 0;
        for (int minutes : this.sessions) {
            if (minutes > longest) {
                longest = minutes;
            }
        }
        return longest;
    }
}
Input
log
25
log
40
total
log
15
longest
quit
Output
> Logged 25 minutes
> Logged 40 minutes
> Total: 65 minutes
> Logged 15 minutes
> Longest: 40 minutes
> Keep practicing!
  • UserInterface keeps the Scanner and the log in instance variables. The constructor gets them as parameters, and from then on every method of the class can use them as this.scanner and this.log.
  • start() holds the loop that used to be in main. It returns when the user quits, and then main ends too.
  • main creates the objects, gives the user interface what it needs, and starts it. It's four lines long, and it stays that short however many commands the program gets.
  • PracticeLog didn't change at all. It never knew who was calling it.

Why the Scanner is a parameter

The user interface could create a Scanner of its own. It's given one instead, for two reasons:

  • A program should have only one Scanner reading System.in. A Scanner reads the input in chunks: to give you one line, it may take in more of what's waiting, and keep the rest for its next calls. A second Scanner on System.in would never see what the first one took in, so it could miss lines.
  • The user interface then works with any Scanner. As module 20 showed, a Scanner can read a String, so the whole program can run from commands written in the code, without anyone typing:
PracticeLog log = new PracticeLog();
UserInterface ui = new UserInterface(new Scanner("log\n30\nlog\n45\nquit\n"), log);
ui.start();
System.out.println(log.total());

This runs log twice and then quit, so it prints the three answers, and then 75. The log was created outside the user interface and handed to it, so log here and this.log in the user interface refer to the same PracticeLog object. That's why the code that created it can still ask it for the total after start() has returned. Some of the hidden checks in this module run your user interface in this way.

Your task

Challenge 1 of 3

A rain barrel in a garden collects rain, and water can be taken from it to water the plants. The logic is ready in RainBarrel.java: rain(int liters) adds water, but never more than fits, take(int liters) returns false and changes nothing if there isn't that much water, and getLiters() returns how much water there is. Main.java is ready too. Write UserInterface.java: its constructor takes a Scanner and a RainBarrel, and start() reads commands, with the prompt > before each one:

  • rain: the next line is a number of liters. Add them to the barrel and print Barrel: and the liters in it, like Barrel: 40 liters.
  • water: the next line is a number of liters. Take them from the barrel and print Barrel: and the liters left, or Not enough water.
  • level: print Barrel: and the liters.
  • quit: print Bye! and return.
  • Anything else: print Unknown command: and the command.

For the input shown, the program prints:

> Barrel: 40 liters
> Barrel: 25 liters
> Not enough water
> Barrel: 100 liters
> Barrel: 100 liters
> Bye!

Challenge 2

Here's the thermostat of the previous step, with its user interface in main. Move the user interface into UserInterface.java: a constructor that takes a Scanner and a Thermostat, and a method start() with the command loop. Then main only creates the Scanner, the thermostat with the target 21, and the user interface, and calls start(). The program prints exactly what it did. For the input shown:

> Target: 21 degrees
> Target: 22 degrees
> Target: 23 degrees
> Target: 22 degrees
> Unknown command: heat
> Thermostat off

Challenge 3

A lemonade stand starts the day with 10 cups of lemonade. LemonadeStand.java is ready, and so is the start() method of UserInterface.java. But the user interface has no instance variables or constructor yet, so it doesn't compile. Add them: the constructor takes a Scanner and a LemonadeStand, and start() uses them as this.scanner and this.stand.

Then write main: create the Scanner, a stand with 10 cups and the user interface, and start it. When start() returns, print Sold today: , the number of cups sold and cups. The stand knows it: ask it with cupsSold(). For the input shown, the program prints:

> Sold 3 cups
> Sold 4 cups
> Cups left: 3
> Only 3 cups left
> Closing the stand
Sold today: 7 cups