Chapter 12 - Java for Beginners Course

Defining our Command Line Interface (CLI)

By: Andres Perez

To allow the user to interact with the vending machine, we will create a class with a simple Command Line Interface.

It presents the options to the user, receives back the user input, and calls the methods of the VendingMachine accordingly.

Why use the CLI as a separate class?

It’s best practice to model the business logic classes separate from the user interface classes. This allows for easier changes in each part/layer of the program.

For example, in the future, we may want to add a web interface to our vending machine, and the separation helps us to keep the business logic of the VendingMachine apart from the different user interfaces we may have.

The CLI constructor and methods

The CLI class will receive the VendingMachine that the user will be interacting with in the constructor.

We will split the functionaly of the CLI into methods that will then be called when we run the vending machine. Let’s see the constructor and some of the simpler methods:

public class CLI {

    private final VendingMachine machine;

    public CLI(VendingMachine machine) {
        this.machine = Objects.requireNonNull(machine, "machine should not be null");
    }

    public void printOptions() {
        System.out.println("Please select an option:");
        System.out.println("1. Add Money");
        System.out.println("2. Request Change");
        System.out.println("3. Deliver Item");
        System.out.println("4. Exit");
    }

    public void printBalance() {
        System.out.println("Current Balance: $" + machine.getBalance());
    }

    public void addMoney(double moneyToAdd) {
        machine.addMoney(moneyToAdd);
    }

    public void giveChange() {
        System.out.println("Your change is: $" + machine.giveChange());
    }

    public boolean deliverItem(String code) {
        boolean success = machine.deliverItem(code);
        if(success) {
            System.out.println("Enjoy your product!");
        } else {
            System.out.println("Unable to deliver your product. Please try again.");
        }
        return success;
    }

}

The basic methods shown allow us to do the operations on the VendingMachine, and display the options using System.out. For the deliverItem method, it also reports to the user if the transaction was successful or not according to the value returned by machine.deliverItem(code).

Variations for printing all the products

Since the products are stored in a Set<ProductDetail>, contained in the ProductCatalog class, we have several options when going over all products and showing them to the user.

For each product, we want to print its productCode, name, and price.

First, let’s create a method that returns these values in the format we want to print them for a given ProductDetail object:

private String getDisplayName(ProductDetail product) {
    return product.getProductCode() +  " " + product.getName() + " " + product.getPrice();
}

Next, we need to go over each product in the catalog. For this, we can use an enhanced for-loop:

public void printAvailableProducts() {
    System.out.println("Products available:");
    for(ProductDetail product : machine.getProductCatalog().getProducts()) {
        System.out.println(getDisplayName(product));
    }
}

Alternatively, we can also create a Stream and use a map operation that receives the ProductDetail object from the Set and returns the desired String with all the information of the product. We print the result of this stream by using a forEach:

public void printAvailableProducts() {
    System.out.println("Products available:");
    machine.getProductCatalog().getProducts().stream()
            .map(product -> getDisplayName(product))
            .forEach(System.out::println);
}
There are multiple ways you could use, we’re just showing a couple in here.

Running the Vending Machine

Finally, we create the runVendingMachine method that actually interacts with the VendingMachine and with the user, and calls the methods described above:

public void run() {
    Scanner scanner = new Scanner(System.in);
    System.out.println("Welcome to our Vending Machine!");
    try {
        while(true) {
            printBalance();
            printAvailableProducts();
            printOptions();

            int option = scanner.nextInt();

            if(option == 1) {
                System.out.println("How much money will you add?");
                double moneyToAdd = scanner.nextDouble();
                addMoney(moneyToAdd);
            } else if(option == 2) {
                giveChange();
            } else if(option == 3) {
                System.out.println("Please select a product using its code:");
                String code = scanner.next();
                deliverItem(code);
            } else if(option == 4) {
                System.out.println("Goodbye.");
                break;
            } else {
                System.out.println("Please select a valid option.");
            }
        }
    } catch(InputMismatchException e) {
        System.out.println("An invalid input was received.");
    } finally {
        scanner.close();
    }
}

First, we create the Scanner object that receives the input from the user. The vending machine should keep running and allow for multiple commands, so we use an infinite while loop to keep the machine running.

Since we are reading int and double types, we enclose the whole block with a try/catch/finally block (explained in Chapter 11) that captures any InputMismatchException that could happen if the user doesn’t input a valid number.

Also, we use the finally block that will always run at the end, no matter what happens in the try and catch blocks, to guarantee the Scanner is correctly closed when the program stops running for any reason.

We can also make sure the Scanner object is correctly closed by using a try-with-resources block. However, we haven’t yet introduced that concept in the course yet.

Inside the while loop, we use the methods explained above to display the balance, the available products, and the options they have.

When the user selects an option, the CLI requests any additional information required for the command selected and calls the corresponding method of its VendingMachine object, which holds the business logic of the program. If the user chooses to exit, the break statement is executed and the while loop stops running.

If not, then the loop will run from the top again and allow the user to give additional commands to the machine.

Can you change the code such that it doesn’t stop if an invalid input is provided? Right now, it will stop if that happens.