I was recently rejected from what looked like a really promising string of interviews. I did very well in a questionnaire style review, and then they handed me this assignment (more or less):
Design a coffee machine which makes different beverages based on set ingredients. The initialization of the recipes for each drink should be hard-coded, although it should be relatively easy to add new drinks. The machine should display the ingredient stock (+cost) and menu upon startup, and after every piece of valid user input. Drink cost is determined by the combination of ingredients. For example, Coffee is 3 units of coffee (75 cents per), 1 unit of sugar (25 cents per), 1 unit of cream (25 cents per). Ingredients and Menu items should be printed in alphabetical order. If the drink is out of stock, it should print accordingly. If the drink is in stock, it should print "Dispensing: ". To select a drink, the user should input a relevant number. If they submit "r" or "R" the ingredients should restock, and "q" or "Q" should quit. Blank lines should be ignored, and invalid input should print an invalid input message.
They supplied the default ingredients (&stock @10) and drinks/recipes.
They told me to "not try and impress", don't overly document (specifically commenting is not required). The code should be scaleable, readable, and just not overly complicated.
Anyway, it's more likely that I made some major error in data structure choice or handling drink selections, than me missing some detail. Still, I can privately send the full assignment description if anyone is curious...I just don't want to put it out there since it's a big company currently hiring. I changed the name of some stuff in my code to make the exact assignment less searchable. I actually have a lot of opinions on things I would change in this code, but I have a hard time believing that they are things that would get me rejected. It must be something bigger...
(I have since been informed that the team was hoping for "A more OO design"...felt like I took it in that direction, personally.)
DrinkMachine
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
public class DrinkMachine{
private static List<Drink> drinkList = new ArrayList<Drink>();
private static List<Ingredient> ingredientList = new ArrayList<Ingredient>();
public static void main(String[] args) {
addAllIngredients();
addAllDrinks();
updateCosts();
updateMakeable();
display();
startIO();
}
public static void startIO(){
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String input = "";
//running loop
while(true){
try {
/*I considered using a switch here for more readable code. Elected to have a dynamic drink menu so drinks can be added
* to addAllDrinks() and this loop would not have to be change. The decision to have slightly less readable code
* in favor of a dynamic menu was made upon my belief that easily modifiable code is incredibly important.
*/
input = reader.readLine().toLowerCase();
if(input.equals("")){
continue;
}else if (input.equals("q")){
System.exit(0);
}else if(input.equals("r")){
restockIngredients();
updateMakeable();
}else if(Integer.parseInt(input) > 0 && Integer.parseInt(input) <= drinkList.size()){ //dynamic drink menu selection
makeDrink(drinkList.get(Integer.parseInt(input)-1));
}else{
throw new IOException();//legal, but invalid input
}
} catch (Exception e) {
System.out.println("Invalid selection: " + input + "\n");//illegal input
}
}//running loop
}
public static void display(){
//The sample IO in the assignment appears to add a blank line after output, so I included that.
System.out.println("Inventory:\n");
for (Ingredient i : ingredientList){
System.out.println(i.getName() + "," + i.getStock() + "\n");
}
System.out.println("Menu:\n");
int count = 1;
for (Drink d : drinkList){
System.out.printf("%d,%s,$%.2f," + d.getMakeable() + "\n\n", count, d.getName(), d.getCost());
count++;
}
}
public static void updateMakeable(){
for (Drink d : drinkList){
Map<String, Integer> currRecipe = d.getRecipe();
for (Ingredient i : ingredientList){
if (currRecipe.containsKey(i.getName()) && i.getStock() < currRecipe.get(i.getName())){
d.setMakeable(false);
break;//check next drink
}
d.setMakeable(true);
}//Ingredient loop
}//Drink loop
}
public static void updateCosts(){
for (Drink d : drinkList){
double currCost = 0;
Map<String, Integer> currRecipe = d.getRecipe();
for (Ingredient i : ingredientList){
if (currRecipe.containsKey(i.getName())){
currCost += i.getCost()*currRecipe.get(i.getName());
}
}//inner
d.setCost(currCost);
}//outer
}
public static void makeDrink(Drink drink){
if(drink.getMakeable()){
System.out.println("Dispensing: " + drink.getName() + "\n");
for (Ingredient i : ingredientList){
if(drink.getRecipe().containsKey(i.getName())){
i.setStock(i.getStock()-drink.getRecipe().get(i.getName()));
}
}
}else{
System.out.println("Out of stock: " + drink.getName() + "\n");
}
updateMakeable();
display();
}
public static void restockIngredients(){
for(Ingredient i : ingredientList){
i.setStock(10);
}
updateMakeable();
display();
}
//Add ingredients through addAllIngredients
public static void addIngredient(Ingredient ingredient){
ingredientList.add(ingredient);
}
//Add drinks through addAllDrinks
public static void addDrink(String name, String[] recipe){
drinkList.add(new Drink(name, recipe));
}
public static void addAllIngredients(){
addIngredient(new Ingredient("Coffee", 0.75));
addIngredient(new Ingredient("Decaf Coffee", 0.75));
addIngredient(new Ingredient("Sugar", 0.25));
addIngredient(new Ingredient("Cream", 0.25));
addIngredient(new Ingredient("Steamed Milk", 0.35));
addIngredient(new Ingredient("Foamed Milk", 0.35));
addIngredient(new Ingredient("Espresso", 1.10));
addIngredient(new Ingredient("Cocoa", 0.90));
addIngredient(new Ingredient("Whipped Cream", 1.00));
Collections.sort(ingredientList);
}
public static void addAllDrinks(){
addDrink("Coffee", new String[]{"Coffee", "Coffee", "Coffee", "Sugar", "Cream"});
addDrink("Decaf Coffee", new String[]{"Decaf Coffee", "Decaf Coffee", "Decaf Coffee", "Sugar", "Cream"});
addDrink("Caffe Latte", new String[]{"Espresso", "Espresso", "Steamed Milk"});
addDrink("Caffe Americano", new String[]{"Espresso", "Espresso", "Espresso"});
addDrink("Caffe Mocha", new String[]{"Espresso", "Cocoa", "Steamed Milk", "Whipped Cream"});
addDrink("Cappuccino", new String[]{"Espresso", "Espresso", "Steamed Milk", "Foamed Milk"});
Collections.sort(drinkList);
}
}
Drink
import java.util.HashMap;
import java.util.Map;
public class Drink implements Comparable<Drink>{
private Map<String, Integer> recipe = new HashMap<String, Integer>();//map ingredients to units per
private String name;
private double totalCost = 0;
private boolean makeable = false;
public Drink(String name, String[] recipe){
this.name = name;
setRecipe(recipe);
}
public int compareTo(Drink drink){
return name.compareTo(drink.getName());
}
public void setRecipe(String[] recipe){
for(String s : recipe){
if(this.recipe.containsKey(s)){
this.recipe.put(s, this.recipe.get(s)+1);//increment if multiple units
}else{
this.recipe.put(s, 1);//insert first occurrence of ingredient
}
}
}
public void setName(String name){
this.name = name;
}
public void setCost(double totalCost){
this.totalCost = totalCost;
}
public void setMakeable(boolean makeable){
this.makeable = makeable;
}
public Map<String, Integer> getRecipe(){
return recipe;
}
public double getCost(){
return totalCost;
}
public String getName(){
return name;
}
public boolean getMakeable(){
return makeable;
}
}
Ingredient
public class Ingredient implements Comparable<Ingredient>{
private String name = "";
private double cost = 0.00;
private int stock = 0;
public Ingredient(String name, double cost){
this.name = name;
this.cost = cost;
this.stock = 10;
}
public int compareTo(Ingredient ingredient) {
return name.compareTo(ingredient.getName());
}
public void setName(String name){
this.name = name;
}
public void setCost(double cost){
this.cost = cost;
}
public void setStock(int stock){
this.stock = stock;
}
public String getName(){
return name;
}
public double getCost(){
return cost;
}
public int getStock(){
return stock;
}
}
addAll*
methods public? They're only used once at initialization. As a general advice: OOP dictates that you need to know as little as possible about the implementation of a class. There should not be apublic
update*
method (if at all), since one should expect an object of a class to behave properly and figure out by itself when it needs updating. It seems that you're seeing OOP as 'let's split my program into useful subroutines' instead of 'I want to use my classes without knowing their exact implementation'. \$\endgroup\$