{"id":"text-ui-new-feature","slug":"adding-a-feature","title":"Adding a feature","text":"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:\n\n- What the program must know or work out goes into the logic class: new instance variables, new methods.\n- The new command, and the sentence it prints, go into the user interface: one more `else if`.\n\nFor `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:\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        UserInterface ui = new UserInterface(scanner, log);\n        ui.start();\n    }\n}\n// ==== PracticeLog.java ====\npublic class PracticeLog {\n    private int totalMinutes;\n    private int longestSession;\n    private int sessions; // new\n\n    public PracticeLog() {\n        this.totalMinutes = 0;\n        this.longestSession = 0;\n        this.sessions = 0; // new\n    }\n\n    public void add(int minutes) {\n        this.totalMinutes = this.totalMinutes + minutes;\n        if (minutes > this.longestSession) {\n            this.longestSession = minutes;\n        }\n        this.sessions++; // new\n    }\n\n    public int total() {\n        return this.totalMinutes;\n    }\n\n    public int longest() {\n        return this.longestSession;\n    }\n\n    public double average() { // new\n        if (this.sessions == 0) {\n            return 0;\n        }\n        return (double) this.totalMinutes / this.sessions;\n    }\n}\n// ==== UserInterface.java ====\nimport java.util.Scanner;\n\npublic class UserInterface {\n    private Scanner scanner;\n    private PracticeLog log;\n\n    public UserInterface(Scanner scanner, PracticeLog log) {\n        this.scanner = scanner;\n        this.log = log;\n    }\n\n    public void start() {\n        while (true) {\n            System.out.print(\"> \");\n            String command = this.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(this.scanner.nextLine());\n                this.log.add(minutes);\n                System.out.println(\"Logged \" + minutes + \" minutes\");\n            } else if (command.equals(\"total\")) {\n                System.out.println(\"Total: \" + this.log.total() + \" minutes\");\n            } else if (command.equals(\"longest\")) {\n                System.out.println(\"Longest: \" + this.log.longest() + \" minutes\");\n            } else if (command.equals(\"average\")) { // new\n                System.out.println(\"Average: \" + this.log.average() + \" minutes\"); // new\n            } else if (command.equals(\"help\")) { // new\n                System.out.println(\"Commands: log, total, longest, average, help, quit\"); // new\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\n    }\n}\n```\n\n```input\nlog\n30\nlog\n45\naverage\nhelp\nquit\n```\n\n```output\n> Logged 30 minutes\n> Logged 45 minutes\n> Average: 37.5 minutes\n> Commands: log, total, longest, average, help, quit\n> Keep practicing!\n```\n\n- `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.\n- `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.\n- `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.\n\n### Try the logic first\n\nWhen 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.\n","task":"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`:\n\n- In `WaterLog.java`, add the method `mlToGoal()`. It returns how many milliliters are still missing, or 0 when the goal is reached.\n- 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`.\n\nFor the input shown, the program prints:\n\n```\n> Total: 500 ml\n> 1500 ml to go\n> Total: 1250 ml\n> 750 ml to go\n> Total: 2100 ml\n> Goal reached!\n> Cheers!\n```\n","kind":"code","seed":"// ==== WaterLog.java ====\npublic class WaterLog {\n    private int goal;\n    private int total;\n\n    public WaterLog(int goal) {\n        this.goal = goal;\n        this.total = 0;\n    }\n\n    public void drink(int ml) {\n        this.total = this.total + ml;\n    }\n\n    public int total() {\n        return this.total;\n    }\n}\n\n// ==== UserInterface.java ====\nimport java.util.Scanner;\n\npublic class UserInterface {\n    private Scanner scanner;\n    private WaterLog log;\n\n    public UserInterface(Scanner scanner, WaterLog log) {\n        this.scanner = scanner;\n        this.log = log;\n    }\n\n    public void start() {\n        while (true) {\n            System.out.print(\"> \");\n            String command = this.scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"Cheers!\");\n                break;\n            }\n            if (command.equals(\"drink\")) {\n                int ml = Integer.valueOf(this.scanner.nextLine());\n                this.log.drink(ml);\n                System.out.println(\"Total: \" + this.log.total() + \" ml\");\n            } else if (command.equals(\"total\")) {\n                System.out.println(\"Total: \" + this.log.total() + \" ml\");\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\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        WaterLog log = new WaterLog(2000);\n        UserInterface ui = new UserInterface(scanner, log);\n        ui.start();\n    }\n}\n","solution":"// ==== WaterLog.java ====\npublic class WaterLog {\n    private int goal;\n    private int total;\n\n    public WaterLog(int goal) {\n        this.goal = goal;\n        this.total = 0;\n    }\n\n    public void drink(int ml) {\n        this.total = this.total + ml;\n    }\n\n    public int total() {\n        return this.total;\n    }\n\n    public int mlToGoal() {\n        if (this.total >= this.goal) {\n            return 0;\n        }\n        return this.goal - this.total;\n    }\n}\n\n// ==== UserInterface.java ====\nimport java.util.Scanner;\n\npublic class UserInterface {\n    private Scanner scanner;\n    private WaterLog log;\n\n    public UserInterface(Scanner scanner, WaterLog log) {\n        this.scanner = scanner;\n        this.log = log;\n    }\n\n    public void start() {\n        while (true) {\n            System.out.print(\"> \");\n            String command = this.scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"Cheers!\");\n                break;\n            }\n            if (command.equals(\"drink\")) {\n                int ml = Integer.valueOf(this.scanner.nextLine());\n                this.log.drink(ml);\n                System.out.println(\"Total: \" + this.log.total() + \" ml\");\n            } else if (command.equals(\"total\")) {\n                System.out.println(\"Total: \" + this.log.total() + \" ml\");\n            } else if (command.equals(\"goal\")) {\n                int missing = this.log.mlToGoal();\n                if (missing == 0) {\n                    System.out.println(\"Goal reached!\");\n                } else {\n                    System.out.println(missing + \" ml to go\");\n                }\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\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        WaterLog log = new WaterLog(2000);\n        UserInterface ui = new UserInterface(scanner, log);\n        ui.start();\n    }\n}\n","hints":["`mlToGoal()` compares two instance variables that WaterLog already has, the total and the goal. It returns a number and prints nothing: the words belong to the user interface, in one more `else if`.","`if (this.total >= this.goal) { return 0; }`, and otherwise `return this.goal - this.total;`. In `start()`, add `else if (command.equals(\"goal\"))` before the last `else`, keep `this.log.mlToGoal()` in a variable, and print one of the two messages depending on whether it's 0."],"tests":[{"name":"Test 1","stdin":"drink\n500\ngoal\ndrink\n750\ngoal\ndrink\n850\ngoal\nquit\n","expect":"> Total: 500 ml\n> 1500 ml to go\n> Total: 1250 ml\n> 750 ml to go\n> Total: 2100 ml\n> Goal reached!\n> Cheers!","hidden":false},{"name":"Test 2","stdin":"goal\ndrink\n2000\ngoal\ntotal\nquit\n","expect":"> 2000 ml to go\n> Total: 2000 ml\n> Goal reached!\n> Total: 2000 ml\n> Cheers!","hidden":true},{"name":"Test 3","stdin":"","call":"WaterLog small = new WaterLog(1000);\nSystem.out.println(small.mlToGoal());\nsmall.drink(400);\nSystem.out.println(small.mlToGoal());\nsmall.drink(900);\nSystem.out.println(small.mlToGoal() + \" \" + small.total());\n","expect":"1000\n600\n0 1300","hidden":true}],"require":[{"pattern":"\\bpublic\\s+int\\s+mlToGoal\\s*\\(\\s*\\)","message":"Add the method to WaterLog as `public int mlToGoal()`."},{"pattern":"\\.mlToGoal\\s*\\(\\s*\\)","message":"The `goal` command asks the log with `mlToGoal()`."}],"forbid":[{"pattern":"\\bclass\\s+WaterLog\\b[\\s\\S]*\\bSystem\\s*\\.\\s*out\\b[\\s\\S]*\\bclass\\s+UserInterface\\b","message":"WaterLog doesn't print. `mlToGoal()` returns a number, and the user interface prints the message."}],"more":[{"task":"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.\n\n- In `Train.java`, add the method `countOf(String kind)`, which returns the number.\n- In `UserInterface.java`, add the command.\n\nFor the input shown, the program prints:\n\n```\n> [engine]-[seats]\n> [engine]-[seats]-[sleeper]\n> [engine]-[seats]-[sleeper]-[dining]\n> [engine]-[seats]-[sleeper]-[dining]-[sleeper]\n> sleeper cars: 2\n> cargo cars: 0\n> [engine]-[seats]-[sleeper]-[dining]\n> sleeper cars: 1\n> End of the line\n```\n","kind":"code","seed":"// ==== Train.java ====\nimport java.util.ArrayList;\n\npublic class Train {\n    private ArrayList<String> cars;\n\n    public Train() {\n        this.cars = new ArrayList<>();\n    }\n\n    public void attach(String kind) {\n        this.cars.add(kind);\n    }\n\n    public boolean detach() {\n        if (this.cars.isEmpty()) {\n            return false;\n        }\n        this.cars.remove(this.cars.size() - 1);\n        return true;\n    }\n\n    public String toString() {\n        String text = \"[engine]\";\n        for (String car : this.cars) {\n            text = text + \"-[\" + car + \"]\";\n        }\n        return text;\n    }\n}\n\n// ==== UserInterface.java ====\nimport java.util.Scanner;\n\npublic class UserInterface {\n    private Scanner scanner;\n    private Train train;\n\n    public UserInterface(Scanner scanner, Train train) {\n        this.scanner = scanner;\n        this.train = train;\n    }\n\n    public void start() {\n        while (true) {\n            System.out.print(\"> \");\n            String command = this.scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"End of the line\");\n                break;\n            }\n            if (command.equals(\"attach\")) {\n                this.train.attach(this.scanner.nextLine());\n                System.out.println(this.train);\n            } else if (command.equals(\"detach\")) {\n                if (this.train.detach()) {\n                    System.out.println(this.train);\n                } else {\n                    System.out.println(\"No cars to detach\");\n                }\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\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        Train train = new Train();\n        UserInterface ui = new UserInterface(scanner, train);\n        ui.start();\n    }\n}\n","solution":"// ==== Train.java ====\nimport java.util.ArrayList;\n\npublic class Train {\n    private ArrayList<String> cars;\n\n    public Train() {\n        this.cars = new ArrayList<>();\n    }\n\n    public void attach(String kind) {\n        this.cars.add(kind);\n    }\n\n    public boolean detach() {\n        if (this.cars.isEmpty()) {\n            return false;\n        }\n        this.cars.remove(this.cars.size() - 1);\n        return true;\n    }\n\n    public int countOf(String kind) {\n        int count = 0;\n        for (String car : this.cars) {\n            if (car.equals(kind)) {\n                count++;\n            }\n        }\n        return count;\n    }\n\n    public String toString() {\n        String text = \"[engine]\";\n        for (String car : this.cars) {\n            text = text + \"-[\" + car + \"]\";\n        }\n        return text;\n    }\n}\n\n// ==== UserInterface.java ====\nimport java.util.Scanner;\n\npublic class UserInterface {\n    private Scanner scanner;\n    private Train train;\n\n    public UserInterface(Scanner scanner, Train train) {\n        this.scanner = scanner;\n        this.train = train;\n    }\n\n    public void start() {\n        while (true) {\n            System.out.print(\"> \");\n            String command = this.scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"End of the line\");\n                break;\n            }\n            if (command.equals(\"attach\")) {\n                this.train.attach(this.scanner.nextLine());\n                System.out.println(this.train);\n            } else if (command.equals(\"detach\")) {\n                if (this.train.detach()) {\n                    System.out.println(this.train);\n                } else {\n                    System.out.println(\"No cars to detach\");\n                }\n            } else if (command.equals(\"count\")) {\n                String kind = this.scanner.nextLine();\n                System.out.println(kind + \" cars: \" + this.train.countOf(kind));\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\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        Train train = new Train();\n        UserInterface ui = new UserInterface(scanner, train);\n        ui.start();\n    }\n}\n","hints":["`countOf` goes through `this.cars` with a for-each loop and counts the cars that `equals` the kind it was given. It returns the count and prints nothing.","The `count` command reads the kind with `this.scanner.nextLine()`, keeps it in a variable, and prints it with `\" cars: \"` and `this.train.countOf(kind)`."],"tests":[{"name":"Test 1","stdin":"attach\nseats\nattach\nsleeper\nattach\ndining\nattach\nsleeper\ncount\nsleeper\ncount\ncargo\ndetach\ncount\nsleeper\nquit\n","expect":"> [engine]-[seats]\n> [engine]-[seats]-[sleeper]\n> [engine]-[seats]-[sleeper]-[dining]\n> [engine]-[seats]-[sleeper]-[dining]-[sleeper]\n> sleeper cars: 2\n> cargo cars: 0\n> [engine]-[seats]-[sleeper]-[dining]\n> sleeper cars: 1\n> End of the line","hidden":false},{"name":"Test 2","stdin":"count\nseats\ndetach\nattach\ncargo\nattach\ncargo\ncount\ncargo\nshunt\nquit\n","expect":"> seats cars: 0\n> No cars to detach\n> [engine]-[cargo]\n> [engine]-[cargo]-[cargo]\n> cargo cars: 2\n> Unknown command: shunt\n> End of the line","hidden":true},{"name":"Test 3","stdin":"","call":"Train train = new Train();\ntrain.attach(\"mail\");\ntrain.attach(\"seats\");\ntrain.attach(\"mail\");\ntrain.attach(\"mail\");\nSystem.out.println(train.countOf(\"mail\") + \" \" + train.countOf(\"seats\") + \" \" + train.countOf(\"Mail\"));\n","expect":"3 1 0","hidden":true}],"require":[{"pattern":"\\bpublic\\s+int\\s+countOf\\s*\\(\\s*String\\s+\\w+\\s*\\)","message":"Add the method to Train as `public int countOf(String kind)`."},{"pattern":"\\.countOf\\s*\\(","message":"The `count` command asks the train with `countOf`."}],"forbid":[{"pattern":"\\bclass\\s+Train\\b[\\s\\S]*\\bSystem\\s*\\.\\s*out\\b[\\s\\S]*\\bclass\\s+UserInterface\\b","message":"Train doesn't print. `countOf` returns a number, and the user interface prints the message."}]},{"task":"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.\n\nThe 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:\n\n```\n> Floor 5\n> Floor 2\n> Floor 2\n> No floor 12\n> Floors traveled: 8\n> Floor 0\n> Floors traveled: 10\n> Out of service\n```\n","kind":"code","seed":"// ==== Elevator.java ====\npublic class Elevator {\n    private int topFloor;\n    private int floor;\n\n    public Elevator(int topFloor) {\n        this.topFloor = topFloor;\n        this.floor = 0;\n    }\n\n    public boolean goTo(int target) {\n        if (target < 0 || target > this.topFloor) {\n            return false;\n        }\n        this.floor = target;\n        return true;\n    }\n\n    public int getFloor() {\n        return this.floor;\n    }\n}\n\n// ==== UserInterface.java ====\nimport java.util.Scanner;\n\npublic class UserInterface {\n    private Scanner scanner;\n    private Elevator elevator;\n\n    public UserInterface(Scanner scanner, Elevator elevator) {\n        this.scanner = scanner;\n        this.elevator = elevator;\n    }\n\n    public void start() {\n        while (true) {\n            System.out.print(\"> \");\n            String command = this.scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"Out of service\");\n                break;\n            }\n            if (command.equals(\"go\")) {\n                int target = Integer.valueOf(this.scanner.nextLine());\n                if (this.elevator.goTo(target)) {\n                    System.out.println(\"Floor \" + this.elevator.getFloor());\n                } else {\n                    System.out.println(\"No floor \" + target);\n                }\n            } else if (command.equals(\"where\")) {\n                System.out.println(\"Floor \" + this.elevator.getFloor());\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\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        Elevator elevator = new Elevator(9);\n        UserInterface ui = new UserInterface(scanner, elevator);\n        ui.start();\n    }\n}\n","solution":"// ==== Elevator.java ====\npublic class Elevator {\n    private int topFloor;\n    private int floor;\n    private int traveled;\n\n    public Elevator(int topFloor) {\n        this.topFloor = topFloor;\n        this.floor = 0;\n        this.traveled = 0;\n    }\n\n    public boolean goTo(int target) {\n        if (target < 0 || target > this.topFloor) {\n            return false;\n        }\n        this.traveled = this.traveled + Math.abs(target - this.floor);\n        this.floor = target;\n        return true;\n    }\n\n    public int getFloor() {\n        return this.floor;\n    }\n\n    public int getTraveled() {\n        return this.traveled;\n    }\n}\n\n// ==== UserInterface.java ====\nimport java.util.Scanner;\n\npublic class UserInterface {\n    private Scanner scanner;\n    private Elevator elevator;\n\n    public UserInterface(Scanner scanner, Elevator elevator) {\n        this.scanner = scanner;\n        this.elevator = elevator;\n    }\n\n    public void start() {\n        while (true) {\n            System.out.print(\"> \");\n            String command = this.scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                System.out.println(\"Out of service\");\n                break;\n            }\n            if (command.equals(\"go\")) {\n                int target = Integer.valueOf(this.scanner.nextLine());\n                if (this.elevator.goTo(target)) {\n                    System.out.println(\"Floor \" + this.elevator.getFloor());\n                } else {\n                    System.out.println(\"No floor \" + target);\n                }\n            } else if (command.equals(\"where\")) {\n                System.out.println(\"Floor \" + this.elevator.getFloor());\n            } else if (command.equals(\"traveled\")) {\n                System.out.println(\"Floors traveled: \" + this.elevator.getTraveled());\n            } else {\n                System.out.println(\"Unknown command: \" + command);\n            }\n        }\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        Elevator elevator = new Elevator(9);\n        UserInterface ui = new UserInterface(scanner, elevator);\n        ui.start();\n    }\n}\n","hints":["Add `private int traveled;`, set to 0 in the constructor, and `public int getTraveled()` that returns it. The distance of one trip is `Math.abs(target - this.floor)`: the difference without its minus sign.","In `goTo`, add the distance after the check for a floor that doesn't exist, and before `this.floor = target;`: after that line, the old floor is gone."],"tests":[{"name":"Test 1","stdin":"go\n5\ngo\n2\nwhere\ngo\n12\ntraveled\ngo\n0\ntraveled\nquit\n","expect":"> Floor 5\n> Floor 2\n> Floor 2\n> No floor 12\n> Floors traveled: 8\n> Floor 0\n> Floors traveled: 10\n> Out of service","hidden":false},{"name":"Test 2","stdin":"traveled\ngo\n9\ngo\n9\ngo\n-1\ntraveled\nup\nquit\n","expect":"> Floors traveled: 0\n> Floor 9\n> Floor 9\n> No floor -1\n> Floors traveled: 9\n> Unknown command: up\n> Out of service","hidden":true},{"name":"Test 3","stdin":"","call":"Elevator small = new Elevator(3);\nsmall.goTo(3);\nsmall.goTo(1);\nsmall.goTo(4);\nsmall.goTo(3);\nSystem.out.println(small.getTraveled() + \" \" + small.getFloor());\n","expect":"7 3","hidden":true}],"require":[{"pattern":"\\bpublic\\s+int\\s+getTraveled\\s*\\(\\s*\\)","message":"Add the method to Elevator as `public int getTraveled()`."},{"pattern":"\\.getTraveled\\s*\\(\\s*\\)","message":"The `traveled` command asks the elevator with `getTraveled()`."}],"forbid":[{"pattern":"\\bclass\\s+Elevator\\b[\\s\\S]*\\bSystem\\s*\\.\\s*out\\b[\\s\\S]*\\bclass\\s+UserInterface\\b","message":"Elevator doesn't print. `getTraveled()` returns a number, and the user interface prints the message."}]}]}