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. |