Java Arena
Step 4 of 4

The command program with a class

Here's the box office again. The state and the rules that guard it move into a class, Concert, and the part of main that reads commands only calls its methods:

Main.java
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Concert concert = new Concert(200);
        while (true) {
            System.out.print("> ");
            String command = scanner.nextLine();
            if (command.equals("quit")) {
                System.out.println("Box office closed");
                break;
            }
            if (command.equals("sell")) {
                int count = Integer.valueOf(scanner.nextLine());
                if (concert.sell(count)) {
                    System.out.println("Sold! Tickets left: " + concert.ticketsLeft());
                } else {
                    System.out.println("Sorry, tickets left: " + concert.ticketsLeft());
                }
            } else if (command.equals("refund")) {
                int count = Integer.valueOf(scanner.nextLine());
                if (concert.refund(count)) {
                    System.out.println("Refunded! Tickets left: " + concert.ticketsLeft());
                } else {
                    System.out.println("Sorry, tickets sold: " + concert.getSold());
                }
            } else if (command.equals("status")) {
                System.out.println("Sold: " + concert.getSold() + ", left: " + concert.ticketsLeft());
            } else {
                System.out.println("Unknown command: " + command);
            }
        }
    }
}
Concert.java
public class Concert {
    private int seats;
    private int sold;

    public Concert(int seats) {
        this.seats = seats;
        this.sold = 0;
    }

    public boolean sell(int count) {
        if (this.sold + count > this.seats) {
            return false;
        }
        this.sold = this.sold + count;
        return true;
    }

    public boolean refund(int count) {
        if (count > this.sold) {
            return false;
        }
        this.sold = this.sold - count;
        return true;
    }

    public int getSold() {
        return this.sold;
    }

    public int ticketsLeft() {
        return this.seats - this.sold;
    }
}
Input
sell
150
sell
80
sell
50
refund
20
refund
300
status
quit
Output
> Sold! Tickets left: 50
> Sorry, tickets left: 50
> Sold! Tickets left: 0
> Refunded! Tickets left: 20
> Sorry, tickets sold: 180
> Sold: 180, left: 20
> Box office closed
  • Concert keeps the state, seats and sold, in private instance variables, and its methods keep the rules. sell and refund return true when they did what was asked and false when they refused, so the caller knows which message to print. The class never reads or prints anything, as in module 25.
  • The loop in main reads the commands and prints the answers. It no longer knows how the limits are checked: it asks concert.sell(count) and looks at the answer.
  • The tickets left are worked out in one place, ticketsLeft().
  • main isn't much shorter than before. The gain isn't in the number of lines, but in where things are, as the next section shows.

Why it helps

The procedural version made three changes hard. Here they are again.

A second concert. The hall adds an afternoon show with 120 seats. That's one more object:

Concert afternoon = new Concert(120);
Concert evening = new Concert(200);

Each object has its own seats and sold, and both follow the same rules, written once in the class. In the procedural version, the afternoon show needed two more variables and a copy of every branch. This step's second challenge sells tickets for both shows.

A new command. Say the box office adds one, which sells a single ticket at the door. In the class version, its branch calls concert.sell(1), and it can't skip the check, because the check is inside sell. Every command that sells tickets goes through the same method, so a rule written once holds for all of them.

A test of the class alone. The rules can be checked without the command loop, and without typing a single command. Here are two JUnit tests for them (module 26):

Concert.java
public class Concert {
    private int seats;
    private int sold;

    public Concert(int seats) {
        this.seats = seats;
        this.sold = 0;
    }

    public boolean sell(int count) {
        if (this.sold + count > this.seats) {
            return false;
        }
        this.sold = this.sold + count;
        return true;
    }

    public boolean refund(int count) {
        if (count > this.sold) {
            return false;
        }
        this.sold = this.sold - count;
        return true;
    }

    public int getSold() {
        return this.sold;
    }

    public int ticketsLeft() {
        return this.seats - this.sold;
    }
}
ConcertTest.java
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;

import org.junit.Test;

public class ConcertTest {
    @Test
    public void cannotSellMoreThanTheSeats() {
        Concert concert = new Concert(10);
        concert.sell(8);
        assertFalse(concert.sell(3));
        assertEquals(2, concert.ticketsLeft());
    }

    @Test
    public void cannotRefundMoreThanWereSold() {
        Concert concert = new Concert(10);
        concert.sell(4);
        assertFalse(concert.refund(5));
        assertEquals(4, concert.getSold());
    }
}
Output
ConcertTest: 2 tests
  cannotRefundMoreThanWereSold: passed
  cannotSellMoreThanTheSeats: passed
2 of 2 tests passed

In the procedural version, the same rules sat inside the loop, between a nextLine and a println, where no unit test could reach them on their own.

Both styles in one program

Look at the class version once more. It isn't object-oriented all the way down. The command loop in main is a sequence of steps with a loop and ifs, and so is the body of sell: check the limit, change the count, return. That's procedural code. What the object adds is the organization: which data belongs together, and which code may change it.

That's how Java programs are usually written. Objects hold the data and the rules that guard it, and inside the methods, the code mostly runs step by step. A calculation that needs no data of its own often stays a static method, like Math.max, which takes two numbers and returns the larger one. And in a bigger program, the command loop would move into a user interface class that takes a Scanner, as in module 25, so that main only creates the objects.

There are other paradigms too. In functional programming, for example, a program is built from functions that compute new values from the values they're given, instead of changing variables step by step. Java supports parts of that style as well, with lambdas and streams, which come later in the course (MOOC part 10).

Your task

Challenge 1 of 3

Now do the same to the bike-share station of the previous step. Main.java is its procedural version, with everything in main. Move the state and the rules into the class in BikeDock.java, and rewrite the branches of main so that they call its methods. The class needs:

  • BikeDock(int slots, int bikes): a station with slots slots, bikes of which hold a bike.
  • rent(int count): if the station has at least count bikes, takes them and returns true. Otherwise it changes nothing and returns false.
  • bringBack(int count): if there are at least count free slots, puts the bikes in them and returns true. Otherwise it changes nothing and returns false.
  • getBikes(): returns the number of bikes at the station.
  • freeSlots(): returns the number of free slots.

The class doesn't print anything. main creates the station with new BikeDock(12, 5) and keeps no count of bikes of its own. The program prints what it did before. For the input shown:

> Enjoy the ride! Bikes here: 2
> Sorry, bikes here: 2
> Thanks! Bikes here: 11
> Sorry, free slots: 1
> Bikes: 11, free slots: 1
> Station closed

Challenge 2

The box office now sells tickets for two shows: an afternoon show with 120 seats, and an evening show with 200. Concert.java is the class above, unchanged. Write main: create one Concert for each show, and read commands, with the prompt > before each one:

  • afternoon: the next line is a number of tickets. Sell them for the afternoon show, and print Sold! Tickets left: and the tickets left for that show. If they don't fit, print Sorry, tickets left: and the tickets left.
  • evening: the same, for the evening show.
  • status: print the tickets left for both shows, like Afternoon: 20 left, evening: 150 left.
  • quit: print Box office closed and end the program.
  • Anything else: print Unknown command: and the command.

For the input shown, the program prints:

> Sold! Tickets left: 20
> Sold! Tickets left: 150
> Sorry, tickets left: 20
> Afternoon: 20 left, evening: 150 left
> Box office closed

Challenge 3

In the procedural version of the station, the only way to try its rules was to run the whole command loop on a list of commands. Now the rules are in BikeDock, and a test can check them alone. BikeDockTest has one test, for renting. Add a second test, for the other rule: bringing back more bikes than there are free slots changes nothing. For example, make a station of 10 slots with 8 bikes, try to bring back 3, and check that bringBack returns false and that the station still has 8 bikes.

The checks run your tests on the BikeDock as it is, where every test should pass, and on BikeDocks with a mistake in bringBack, where your new test should fail.