{"id":"class-design-objects","slug":"from-variables-to-objects","title":"Turning a program into objects","text":"This module is about designing classes: deciding what a class should hold and do, and giving it several constructors, or several methods of the same name. It starts with why a class of your own is worth writing at all.\n\n### A program of plain variables\n\nHere's a program that follows two people's steps for a day, written the way you'd have written it before module 17. The goal is 10000 steps a day.\n\n```java\nString firstName = \"Noor\";\nint firstSteps = 0;\nString secondName = \"Theo\";\nint secondSteps = 0;\n\nfirstSteps = firstSteps + 6200;\nsecondSteps = secondSteps + 3100;\nfirstSteps = firstSteps + 4500;\n\nif (firstSteps >= 10000) {\n    System.out.println(firstName + \": \" + firstSteps + \" steps, goal reached\");\n} else {\n    System.out.println(firstName + \": \" + firstSteps + \" steps\");\n}\nif (secondSteps >= 10000) {\n    System.out.println(secondName + \": \" + secondSteps + \" steps, goal reached\");\n} else {\n    System.out.println(secondName + \": \" + secondSteps + \" steps\");\n}\n```\n\n```output\nNoor: 10700 steps, goal reached\nTheo: 3100 steps\n```\n\nIt works, but look at what it takes:\n\n- Four variables, and only their names say that `firstName` and `firstSteps` belong together.\n- The goal check is written twice. To change the goal, you have to find and change both copies.\n- A third person means two more variables and a copy of every line that uses them. Write `firstSteps` in one of the copies where you meant `secondSteps`, and the program adds the steps to the wrong person, without any error message.\n\n### The same program with objects\n\nEach person has a name and a number of steps, and the same goal applies to everyone. That's a class: a `StepCounter` holds one person's name and steps, and knows the goal.\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        StepCounter noor = new StepCounter(\"Noor\");\n        StepCounter theo = new StepCounter(\"Theo\");\n\n        noor.walk(6200);\n        theo.walk(3100);\n        noor.walk(4500);\n\n        System.out.println(noor);\n        System.out.println(theo);\n    }\n}\n// ==== StepCounter.java ====\npublic class StepCounter {\n    private String name;\n    private int steps;\n\n    public StepCounter(String name) {\n        this.name = name;\n        this.steps = 0;\n    }\n\n    public void walk(int steps) {\n        this.steps = this.steps + steps;\n    }\n\n    public String toString() {\n        if (this.steps >= 10000) {\n            return this.name + \": \" + this.steps + \" steps, goal reached\";\n        }\n        return this.name + \": \" + this.steps + \" steps\";\n    }\n}\n```\n\n```output\nNoor: 10700 steps, goal reached\nTheo: 3100 steps\n```\n\nThe output is the same, but the program reads differently:\n\n- `main` tells what happens in the words of the problem: Noor walks 6200 steps, Theo walks 3100. How the steps are added up is the class's business.\n- Each person's name and steps travel together in one object, so they can't get mixed up with someone else's.\n- The goal is written once, in `toString`. Change it there, and it changes for everyone.\n- A third person is one more line: `StepCounter maya = new StepCounter(\"Maya\");`.\n\nThis is the idea behind **object-oriented programming**: a program is built from objects, and each object looks after its own data and the rules that go with it. A class stands for one idea of the problem (here, a step counter), its instance variables are what each object must remember, and its methods are what can be done with it. The rest of the program only calls the methods, just as it uses an ArrayList without knowing how it works inside.\n\nA class is worth it even when there's only one object. A program about a single person is still easier to read with a `StepCounter`, because the names of the class and its methods say what the variables are for.\n","task":"In a game, a hero starts with 100 health. A hit takes health away, but never below 0, and healing gives health back, but never above 100. `Main.java` is ready and uses a `Hero` object. Write the class in `Hero.java`:\n\n- `Hero(String name)` makes a hero with 100 health.\n- `hit(int damage)` takes `damage` off the health. The health doesn't go below 0.\n- `heal(int amount)` adds `amount` to the health. The health doesn't go above 100.\n- `toString()` returns the name and the health, as in the output below.\n\nThe program prints:\n\n```\nAria, health 90\nAria, health 100\nAria, health 0\n```\n","kind":"code","seed":"// ==== Hero.java ====\npublic class Hero {\n\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Hero hero = new Hero(\"Aria\");\n        hero.hit(30);\n        hero.heal(20);\n        System.out.println(hero);\n        hero.heal(50);\n        System.out.println(hero);\n        hero.hit(120);\n        System.out.println(hero);\n    }\n}\n","solution":"// ==== Hero.java ====\npublic class Hero {\n    private String name;\n    private int health;\n\n    public Hero(String name) {\n        this.name = name;\n        this.health = 100;\n    }\n\n    public void hit(int damage) {\n        this.health = this.health - damage;\n        if (this.health < 0) {\n            this.health = 0;\n        }\n    }\n\n    public void heal(int amount) {\n        this.health = this.health + amount;\n        if (this.health > 100) {\n            this.health = 100;\n        }\n    }\n\n    public String toString() {\n        return this.name + \", health \" + this.health;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Hero hero = new Hero(\"Aria\");\n        hero.hit(30);\n        hero.heal(20);\n        System.out.println(hero);\n        hero.heal(50);\n        System.out.println(hero);\n        hero.hit(120);\n        System.out.println(hero);\n    }\n}\n","hints":["A hero must remember two things: `private String name;` and `private int health;`. The constructor stores the name and sets the health to 100.","`hit` subtracts and then sets the health back to 0 if it went below 0. `heal` adds and then sets it back to 100 if it went above 100. `toString` returns `this.name + \", health \" + this.health`."],"tests":[{"name":"The program","stdin":"","expect":"Aria, health 90\nAria, health 100\nAria, health 0","hidden":false},{"name":"Test 2","stdin":"","call":"Hero hero = new Hero(\"Bram\");\nSystem.out.println(hero);\nhero.hit(100);\nSystem.out.println(hero);\nhero.heal(35);\nSystem.out.println(hero);\n","expect":"Bram, health 100\nBram, health 0\nBram, health 35","hidden":true},{"name":"Test 3","stdin":"","call":"Hero first = new Hero(\"Cleo\");\nHero second = new Hero(\"Dev\");\nfirst.hit(45);\nsecond.hit(5);\nsecond.heal(3);\nSystem.out.println(first);\nSystem.out.println(second);\n","expect":"Cleo, health 55\nDev, health 98","hidden":true},{"name":"Test 4","stdin":"","call":"Hero hero = new Hero(\"Eli\");\nhero.heal(10);\nSystem.out.println(hero);\nhero.hit(250);\nhero.hit(10);\nSystem.out.println(hero);\n","expect":"Eli, health 100\nEli, health 0","hidden":true}],"require":[{"pattern":"\\bprivate\\s+\\w+\\s+\\w+\\s*[;=]","min":2,"message":"Keep the hero's name and health in private instance variables."}],"forbid":[],"more":[{"task":"This program counts the visitors at the two gates of a zoo. It reads a line for each visitor, `north` or `south`, until the line `end`, and then prints both counts. Any other line is skipped. It keeps the counts in plain variables. `Gate.java` holds a class that does the counting: rewrite `main` so it uses two Gate objects instead of the two counters, gives each visitor to the right gate with `pass()`, and prints the gates. For the input `north`, `south`, `north` and `end`, one per line, the program still prints:\n\n```\nNorth gate: 2\nSouth gate: 1\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        int northVisitors = 0;\n        int southVisitors = 0;\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.equals(\"end\")) {\n                break;\n            }\n            if (line.equals(\"north\")) {\n                northVisitors++;\n            } else if (line.equals(\"south\")) {\n                southVisitors++;\n            }\n        }\n        System.out.println(\"North gate: \" + northVisitors);\n        System.out.println(\"South gate: \" + southVisitors);\n    }\n}\n\n// ==== Gate.java ====\npublic class Gate {\n    private String name;\n    private int visitors;\n\n    public Gate(String name) {\n        this.name = name;\n        this.visitors = 0;\n    }\n\n    public void pass() {\n        this.visitors = this.visitors + 1;\n    }\n\n    public String toString() {\n        return this.name + \" gate: \" + this.visitors;\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        Gate north = new Gate(\"North\");\n        Gate south = new Gate(\"South\");\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.equals(\"end\")) {\n                break;\n            }\n            if (line.equals(\"north\")) {\n                north.pass();\n            } else if (line.equals(\"south\")) {\n                south.pass();\n            }\n        }\n        System.out.println(north);\n        System.out.println(south);\n    }\n}\n\n// ==== Gate.java ====\npublic class Gate {\n    private String name;\n    private int visitors;\n\n    public Gate(String name) {\n        this.name = name;\n        this.visitors = 0;\n    }\n\n    public void pass() {\n        this.visitors = this.visitors + 1;\n    }\n\n    public String toString() {\n        return this.name + \" gate: \" + this.visitors;\n    }\n}\n","hints":["Replace the two counters with two gates before the loop: `Gate north = new Gate(\"North\");` and `Gate south = new Gate(\"South\");`.","In the loop, `north.pass();` instead of `northVisitors++;`, and the same for south. At the end, `System.out.println(north);` prints the gate with its `toString`."],"tests":[{"name":"Test 1","stdin":"north\nsouth\nnorth\nend\n","expect":"North gate: 2\nSouth gate: 1","hidden":false},{"name":"Test 2","stdin":"end\n","expect":"North gate: 0\nSouth gate: 0","hidden":true},{"name":"Test 3","stdin":"south\nsouth\nsouth\nnorth\nsouth\nend\n","expect":"North gate: 1\nSouth gate: 4","hidden":true},{"name":"Test 4","stdin":"west\nnorth\nend\n","expect":"North gate: 1\nSouth gate: 0","hidden":true}],"require":[{"pattern":"\\bnew\\s+Gate\\s*\\(","min":2,"message":"Create two gates with `new Gate(...)`, one for each side."},{"pattern":"\\.pass\\s*\\(\\s*\\)","message":"Count each visitor with the gate's `pass()` method."}],"forbid":[{"pattern":"\\b(northVisitors|southVisitors)\\b","message":"Let the Gate objects do the counting: remove `northVisitors` and `southVisitors` from `main`."}]},{"task":"What does this program print? It uses the `Hero` class from the first challenge, with two heroes.\n","kind":"predict","seed":"public class Main {\n    public static void main(String[] args) {\n        Hero aria = new Hero(\"Aria\");\n        Hero bram = new Hero(\"Bram\");\n        aria.hit(40);\n        bram.hit(70);\n        bram.heal(50);\n        aria.hit(80);\n        aria.heal(10);\n        System.out.println(aria);\n        System.out.println(bram);\n    }\n}\n// ==== Hero.java ====\npublic class Hero {\n    private String name;\n    private int health;\n\n    public Hero(String name) {\n        this.name = name;\n        this.health = 100;\n    }\n\n    public void hit(int damage) {\n        this.health = this.health - damage;\n        if (this.health < 0) {\n            this.health = 0;\n        }\n    }\n\n    public void heal(int amount) {\n        this.health = this.health + amount;\n        if (this.health > 100) {\n            this.health = 100;\n        }\n    }\n\n    public String toString() {\n        return this.name + \", health \" + this.health;\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        Hero aria = new Hero(\"Aria\");\n        Hero bram = new Hero(\"Bram\");\n        aria.hit(40);\n        bram.hit(70);\n        bram.heal(50);\n        aria.hit(80);\n        aria.heal(10);\n        System.out.println(aria);\n        System.out.println(bram);\n    }\n}\n// ==== Hero.java ====\npublic class Hero {\n    private String name;\n    private int health;\n\n    public Hero(String name) {\n        this.name = name;\n        this.health = 100;\n    }\n\n    public void hit(int damage) {\n        this.health = this.health - damage;\n        if (this.health < 0) {\n            this.health = 0;\n        }\n    }\n\n    public void heal(int amount) {\n        this.health = this.health + amount;\n        if (this.health > 100) {\n            this.health = 100;\n        }\n    }\n\n    public String toString() {\n        return this.name + \", health \" + this.health;\n    }\n}\n","hints":["Follow each hero on their own. Aria goes from 100 to 60, and then a hit of 80 would take her below 0.","Bram goes from 100 to 30, and healing 50 takes him to 80, which is still under the limit of 100."],"tests":[{"name":"Output","stdin":"","expect":"Aria, health 10\nBram, health 80","hidden":false}],"require":[],"forbid":[],"lines":["Aria, health 10","Bram, health 80"]}]}