{"id":"text-ui-logic-class","slug":"logic-class","title":"The program's logic in a class","text":"The practice log's `main` does two jobs at once. It talks with the user: it prints the prompt, reads commands and prints answers. And it does the work: it keeps the sessions, adds them up and finds the longest. The two are woven together, and each new command makes the knot bigger. The first step toward untangling them is to move the work into a class of its own:\n\n```java\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        PracticeLog log = new PracticeLog();\n        while (true) {\n            System.out.print(\"> \");\n            String command = scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"Keep practicing!\");\n                break;\n            }\n            if (command.equals(\"log\")) {\n                int minutes = Integer.valueOf(scanner.nextLine());\n                log.add(minutes);\n                System.out.println(\"Logged \" + minutes + \" minutes\");\n            } else if (command.equals(\"total\")) {\n                System.out.println(\"Total: \" + log.total() + \" minutes\");\n            } else if (command.equals(\"longest\")) {\n                System.out.println(\"Longest: \" + log.longest() + \" minutes\");\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\n    }\n}\n// ==== PracticeLog.java ====\nimport java.util.ArrayList;\n\npublic class PracticeLog {\n    private ArrayList<Integer> sessions;\n\n    public PracticeLog() {\n        this.sessions = new ArrayList<>();\n    }\n\n    public void add(int minutes) {\n        this.sessions.add(minutes);\n    }\n\n    public int total() {\n        int total = 0;\n        for (int minutes : this.sessions) {\n            total = total + minutes;\n        }\n        return total;\n    }\n\n    public int longest() {\n        int longest = 0;\n        for (int minutes : this.sessions) {\n            if (minutes > longest) {\n                longest = minutes;\n            }\n        }\n        return longest;\n    }\n}\n```\n\n```input\nlog\n25\nlog\n40\ntotal\nlog\n15\nlongest\nquit\n```\n\n```output\n> Logged 25 minutes\n> Logged 40 minutes\n> Total: 65 minutes\n> Logged 15 minutes\n> Longest: 40 minutes\n> Keep practicing!\n```\n\nThe program does exactly what it did before, but the jobs are now apart:\n\n- `PracticeLog` is the program's **logic**: what the program knows and works out. It keeps the sessions and answers questions about them with return values. It never reads input and never prints anything.\n- `main` is the **user interface**, or UI: the part that talks with the user. It reads the commands, calls the log's methods, and turns their answers into sentences.\n- The loop in `main` now reads like a list of the commands. How the total is added up is no longer its business: `log.total()` says what it needs.\n\n### Why the split helps\n\n- Each part can be read and changed on its own. To change what a message says, you look at the UI. To change how the longest session is found, you look at `PracticeLog`, and the UI doesn't care.\n- The logic can be used without anyone typing anything: `PracticeLog log = new PracticeLog(); log.add(30); System.out.println(log.total());`. That's how the checks on this site test your classes, and module 26 builds on it.\n- The same class could serve another kind of user interface, such as an app with buttons, without a change. That's why it returns numbers and doesn't print sentences: how an answer is shown is for the UI to decide.\n\nA quick test for a logic class: could a program without a keyboard or a screen use it? If the class has a Scanner or a `System.out.println` in it, it couldn't.\n","task":"Here's the campsite of the previous step, split in two. `Main.java` is the user interface, and it's ready. Write the logic class in `Campsite.java`:\n\n- `Campsite(int spots)`: an empty campsite with that many tent spots.\n- `checkIn()`: if a spot is free, one more spot is taken and the method returns `true`. If every spot is taken, nothing changes and it returns `false`.\n- `checkOut()`: if a spot is taken, it's free again and the method returns `true`. If no spot is taken, nothing changes and it returns `false`.\n- `freeSpots()`: returns the number of free spots.\n\nThe class doesn't print anything: `main` does. For the input shown, the program prints:\n\n```\n> Welcome! Free spots: 2\n> Welcome! Free spots: 1\n> Free spots: 1\n> Welcome! Free spots: 0\n> Sorry, the campsite is full\n> Safe travels! Free spots: 1\n> Lights out\n```\n","kind":"code","seed":"// ==== Campsite.java ====\npublic class Campsite {\n    // Write the instance variables, the constructor and the methods here.\n}\n\n// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        Campsite campsite = new Campsite(3);\n        while (true) {\n            System.out.print(\"> \");\n            String command = scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"Lights out\");\n                break;\n            }\n            if (command.equals(\"arrive\")) {\n                if (campsite.checkIn()) {\n                    System.out.println(\"Welcome! Free spots: \" + campsite.freeSpots());\n                } else {\n                    System.out.println(\"Sorry, the campsite is full\");\n                }\n            } else if (command.equals(\"depart\")) {\n                if (campsite.checkOut()) {\n                    System.out.println(\"Safe travels! Free spots: \" + campsite.freeSpots());\n                } else {\n                    System.out.println(\"Nobody is camping\");\n                }\n            } else if (command.equals(\"spots\")) {\n                System.out.println(\"Free spots: \" + campsite.freeSpots());\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\n    }\n}\n","solution":"// ==== Campsite.java ====\npublic class Campsite {\n    // Write the instance variables, the constructor and the methods here.\n    private int spots;\n    private int taken;\n\n    public Campsite(int spots) {\n        this.spots = spots;\n        this.taken = 0;\n    }\n\n    public boolean checkIn() {\n        if (this.taken >= this.spots) {\n            return false;\n        }\n        this.taken++;\n        return true;\n    }\n\n    public boolean checkOut() {\n        if (this.taken == 0) {\n            return false;\n        }\n        this.taken--;\n        return true;\n    }\n\n    public int freeSpots() {\n        return this.spots - this.taken;\n    }\n}\n\n// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        Campsite campsite = new Campsite(3);\n        while (true) {\n            System.out.print(\"> \");\n            String command = scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"Lights out\");\n                break;\n            }\n            if (command.equals(\"arrive\")) {\n                if (campsite.checkIn()) {\n                    System.out.println(\"Welcome! Free spots: \" + campsite.freeSpots());\n                } else {\n                    System.out.println(\"Sorry, the campsite is full\");\n                }\n            } else if (command.equals(\"depart\")) {\n                if (campsite.checkOut()) {\n                    System.out.println(\"Safe travels! Free spots: \" + campsite.freeSpots());\n                } else {\n                    System.out.println(\"Nobody is camping\");\n                }\n            } else if (command.equals(\"spots\")) {\n                System.out.println(\"Free spots: \" + campsite.freeSpots());\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\n    }\n}\n","hints":["The campsite remembers two numbers: how many spots it has, given to the constructor, and how many are taken, which starts at 0. `freeSpots()` works out its answer from those two.","In `checkIn()`, return `false` first when `this.taken >= this.spots`. Otherwise add one and return `true`. `checkOut()` works the same way, with `this.taken == 0`. `freeSpots()` returns `this.spots - this.taken`."],"tests":[{"name":"Test 1","stdin":"arrive\narrive\nspots\narrive\narrive\ndepart\nquit\n","expect":"> Welcome! Free spots: 2\n> Welcome! Free spots: 1\n> Free spots: 1\n> Welcome! Free spots: 0\n> Sorry, the campsite is full\n> Safe travels! Free spots: 1\n> Lights out","hidden":false},{"name":"Test 2","stdin":"depart\narrive\nspots\ndepart\ndepart\nquit\n","expect":"> Nobody is camping\n> Welcome! Free spots: 2\n> Free spots: 2\n> Safe travels! Free spots: 3\n> Nobody is camping\n> Lights out","hidden":true},{"name":"Test 3","stdin":"","call":"Campsite tiny = new Campsite(1);\nSystem.out.println(tiny.checkIn() + \" \" + tiny.checkIn() + \" \" + tiny.freeSpots());\nSystem.out.println(tiny.checkOut() + \" \" + tiny.checkOut() + \" \" + tiny.freeSpots());\n","expect":"true false 0\ntrue false 1","hidden":true},{"name":"Test 4","stdin":"","call":"Campsite big = new Campsite(5);\nCampsite other = new Campsite(2);\nfor (int i = 0; i < 7; i++) {\n    big.checkIn();\n}\nother.checkIn();\nSystem.out.println(big.freeSpots() + \" \" + other.freeSpots());\n","expect":"0 1","hidden":true}],"require":[],"forbid":[{"pattern":"\\bclass\\s+Campsite\\b[\\s\\S]*\\bSystem\\s*\\.\\s*out\\b[\\s\\S]*\\bclass\\s+Main\\b","message":"The Campsite class doesn't print. It returns `true` or `false`, and `main` prints the messages."}],"more":[{"task":"A thermostat's logic is ready in `Thermostat.java`. `warmer()` and `cooler()` change the target temperature by one degree and return `true`. If the target is already at its limit, 28 or 15 degrees, they change nothing and return `false`. `getTarget()` returns the target.\n\nWrite the user interface in `Main.java`: create a Thermostat with the target 21, and read commands, with the prompt `> ` before each one:\n\n- `up`: make it one degree warmer and print the new target like `Target: 22 degrees`, or `Already at the maximum` when it can't go higher.\n- `down`: make it one degree cooler and print the new target, or `Already at the minimum`.\n- `show`: print the target like `Target: 21 degrees`.\n- `off`: print `Thermostat off` and end the program.\n- Anything else: print `Unknown command: ` and the command.\n\nFor the input shown, the program prints:\n\n```\n> Target: 21 degrees\n> Target: 22 degrees\n> Target: 23 degrees\n> Target: 22 degrees\n> Unknown command: heat\n> Thermostat off\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        // Create the thermostat, then write the command loop.\n    }\n}\n\n// ==== Thermostat.java ====\npublic class Thermostat {\n    private int target;\n\n    public Thermostat(int target) {\n        this.target = target;\n    }\n\n    public boolean warmer() {\n        if (this.target >= 28) {\n            return false;\n        }\n        this.target++;\n        return true;\n    }\n\n    public boolean cooler() {\n        if (this.target <= 15) {\n            return false;\n        }\n        this.target--;\n        return true;\n    }\n\n    public int getTarget() {\n        return this.target;\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        // Create the thermostat, then write the command loop.\n        Thermostat thermostat = new Thermostat(21);\n        while (true) {\n            System.out.print(\"> \");\n            String command = scanner.nextLine();\n            if (command.equals(\"off\")) {\n                System.out.println(\"Thermostat off\");\n                break;\n            }\n            if (command.equals(\"up\")) {\n                if (thermostat.warmer()) {\n                    System.out.println(\"Target: \" + thermostat.getTarget() + \" degrees\");\n                } else {\n                    System.out.println(\"Already at the maximum\");\n                }\n            } else if (command.equals(\"down\")) {\n                if (thermostat.cooler()) {\n                    System.out.println(\"Target: \" + thermostat.getTarget() + \" degrees\");\n                } else {\n                    System.out.println(\"Already at the minimum\");\n                }\n            } else if (command.equals(\"show\")) {\n                System.out.println(\"Target: \" + thermostat.getTarget() + \" degrees\");\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\n    }\n}\n\n// ==== Thermostat.java ====\npublic class Thermostat {\n    private int target;\n\n    public Thermostat(int target) {\n        this.target = target;\n    }\n\n    public boolean warmer() {\n        if (this.target >= 28) {\n            return false;\n        }\n        this.target++;\n        return true;\n    }\n\n    public boolean cooler() {\n        if (this.target <= 15) {\n            return false;\n        }\n        this.target--;\n        return true;\n    }\n\n    public int getTarget() {\n        return this.target;\n    }\n}\n","hints":["`Thermostat thermostat = new Thermostat(21);` goes before the loop. The loop is shaped like the campsite's, with `off` in the place of `quit`.","`if (thermostat.warmer()) { ... } else { ... }` both changes the target and tells which message to print. After it, `thermostat.getTarget()` gives the new target."],"tests":[{"name":"Test 1","stdin":"show\nup\nup\ndown\nheat\noff\n","expect":"> Target: 21 degrees\n> Target: 22 degrees\n> Target: 23 degrees\n> Target: 22 degrees\n> Unknown command: heat\n> Thermostat off","hidden":false},{"name":"Test 2","stdin":"up\nup\nup\nup\nup\nup\nup\nup\nshow\noff\n","expect":"> Target: 22 degrees\n> Target: 23 degrees\n> Target: 24 degrees\n> Target: 25 degrees\n> Target: 26 degrees\n> Target: 27 degrees\n> Target: 28 degrees\n> Already at the maximum\n> Target: 28 degrees\n> Thermostat off","hidden":true},{"name":"Test 3","stdin":"down\ndown\ndown\ndown\ndown\ndown\ndown\nDown\nup\noff\n","expect":"> Target: 20 degrees\n> Target: 19 degrees\n> Target: 18 degrees\n> Target: 17 degrees\n> Target: 16 degrees\n> Target: 15 degrees\n> Already at the minimum\n> Unknown command: Down\n> Target: 16 degrees\n> Thermostat off","hidden":true}],"require":[{"pattern":"new\\s+Thermostat\\s*\\(\\s*21\\s*\\)","message":"Create the thermostat with `new Thermostat(21)`."},{"pattern":"\\.warmer\\s*\\(\\s*\\)","message":"Let the thermostat change the target: call `warmer()` for `up`."},{"pattern":"\\.cooler\\s*\\(\\s*\\)","message":"Let the thermostat change the target: call `cooler()` for `down`."}],"forbid":[]},{"task":"Here's the deli counter of the previous step, with everything in `main`. It works, but the list and the work on it are tangled with the talking. Move the logic into `DeliLine.java`, so that `main` keeps only the prompt, the reading and the printing. The class needs:\n\n- `DeliLine()`: an empty line.\n- `take(String name)`: adds the name to the end of the line and returns their place in it, 1 for the first.\n- `isEmpty()`: returns `true` when nobody is waiting.\n- `next()`: takes the first name off the line and returns it. `main` only calls it when someone is waiting.\n- `toString()`: returns the names in line, like `[Ben, Cleo]`.\n\nThen change `main` to use a DeliLine instead of the list. The program prints exactly what it did. For the input shown:\n\n```\n> Ana, you're number 1\n> Ben, you're number 2\n> Now serving Ana\n> Cleo, you're number 2\n> In line: [Ben, Cleo]\n> Now serving Ben\n> Now serving Cleo\n> Nobody is waiting\n> Closed for today\n```\n","kind":"code","seed":"// ==== DeliLine.java ====\nimport java.util.ArrayList;\n\npublic class DeliLine {\n    // Move the list and the work on it here.\n}\n\n// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        ArrayList<String> names = new ArrayList<>();\n        while (true) {\n            System.out.print(\"> \");\n            String command = scanner.nextLine();\n            if (command.equals(\"close\")) {\n                System.out.println(\"Closed for today\");\n                break;\n            }\n            if (command.equals(\"take\")) {\n                String name = scanner.nextLine();\n                names.add(name);\n                System.out.println(name + \", you're number \" + names.size());\n            } else if (command.equals(\"next\")) {\n                if (names.isEmpty()) {\n                    System.out.println(\"Nobody is waiting\");\n                } else {\n                    System.out.println(\"Now serving \" + names.get(0));\n                    names.remove(0);\n                }\n            } else if (command.equals(\"queue\")) {\n                System.out.println(\"In line: \" + names);\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\n    }\n}\n","solution":"// ==== DeliLine.java ====\nimport java.util.ArrayList;\n\npublic class DeliLine {\n    // Move the list and the work on it here.\n    private ArrayList<String> names;\n\n    public DeliLine() {\n        this.names = new ArrayList<>();\n    }\n\n    public int take(String name) {\n        this.names.add(name);\n        return this.names.size();\n    }\n\n    public boolean isEmpty() {\n        return this.names.isEmpty();\n    }\n\n    public String next() {\n        String first = this.names.get(0);\n        this.names.remove(0);\n        return first;\n    }\n\n    public String toString() {\n        return this.names.toString();\n    }\n}\n\n// ==== Main.java ====\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        DeliLine line = new DeliLine();\n        while (true) {\n            System.out.print(\"> \");\n            String command = scanner.nextLine();\n            if (command.equals(\"close\")) {\n                System.out.println(\"Closed for today\");\n                break;\n            }\n            if (command.equals(\"take\")) {\n                String name = scanner.nextLine();\n                int place = line.take(name);\n                System.out.println(name + \", you're number \" + place);\n            } else if (command.equals(\"next\")) {\n                if (line.isEmpty()) {\n                    System.out.println(\"Nobody is waiting\");\n                } else {\n                    System.out.println(\"Now serving \" + line.next());\n                }\n            } else if (command.equals(\"queue\")) {\n                System.out.println(\"In line: \" + line);\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\n    }\n}\n","hints":["The list becomes an instance variable of DeliLine, created in its constructor. Each method does what `main` did with the list: `take` adds and returns `this.names.size()`, `next` keeps `this.names.get(0)` in a variable, removes it and returns it.","`toString()` can return `this.names.toString()`, the list's own text. In `main`, create `DeliLine line = new DeliLine();` and replace each use of the list with a call, such as `line.take(name)`."],"tests":[{"name":"Test 1","stdin":"take\nAna\ntake\nBen\nnext\ntake\nCleo\nqueue\nnext\nnext\nnext\nclose\n","expect":"> Ana, you're number 1\n> Ben, you're number 2\n> Now serving Ana\n> Cleo, you're number 2\n> In line: [Ben, Cleo]\n> Now serving Ben\n> Now serving Cleo\n> Nobody is waiting\n> Closed for today","hidden":false},{"name":"Test 2","stdin":"next\nqueue\ntake\nMia\ntake\nLeo\ntake\nMia\nqueue\nnext\nqueue\nserve\nclose\n","expect":"> Nobody is waiting\n> In line: []\n> Mia, you're number 1\n> Leo, you're number 2\n> Mia, you're number 3\n> In line: [Mia, Leo, Mia]\n> Now serving Mia\n> In line: [Leo, Mia]\n> Unknown command: serve\n> Closed for today","hidden":true},{"name":"Test 3","stdin":"","call":"DeliLine line = new DeliLine();\nSystem.out.println(line.isEmpty() + \" \" + line);\nSystem.out.println(line.take(\"Ana\") + \" \" + line.take(\"Ben\") + \" \" + line.take(\"Cleo\"));\nSystem.out.println(line.next() + \" \" + line.isEmpty() + \" \" + line);\n","expect":"true []\n1 2 3\nAna false [Ben, Cleo]","hidden":true}],"require":[],"forbid":[{"pattern":"\\bclass\\s+Main\\b[\\s\\S]*\\bArrayList\\b","message":"`main` shouldn't keep a list any more: the list belongs in DeliLine."},{"pattern":"\\bclass\\s+DeliLine\\b[\\s\\S]*\\bSystem\\s*\\.\\s*out\\b[\\s\\S]*\\bclass\\s+Main\\b","message":"DeliLine doesn't print. It returns its answers, and `main` prints them."}]}]}