Home > front end >  Is there a way I can break up into more than 2 classes
Is there a way I can break up into more than 2 classes

Time:05-07

I am wondering how/if I could possibly break this up into multiple classes but keep the values across objects to pass between both. I am unable to get things working including using a constructor - the values from my first object won't go over to my second. I've tried to copy them to the other class object using the copy method to no avail. Everything I find online uses only 2 classes, but I'd like to break it up into at least 3-4 (If possible separating the WantToCook and CookAgain methods into their own class). I know there are all sorts of other improvements I could make, but due to time constraints they can no longer be a priority for me. Please help if it's even possible.

My project inputs ingredients and tells the user what recipes can be made with them. The user "cooks" a recipe and then the ingredients are deducted from their initial counts. The user ideally keeps doing this until no ingredients remain, but as you will see the values go into the negative. That's one of the many things I wanted to fix in addition to this but it's no longer a priority for me.

Here is my problem child eyesore class:

import java.util.Scanner;

//object class
public class ShowRecipes {
    
    Scanner kbd = new Scanner(System.in);

    //ingredient names array
    public String ingredientsNames[] = {"Apples", "Cherry", "Carrot", "Flour ", "Sugar "};

    //ingredients array - setters will set
    public int[] ingredients = new int[5];

    //Basic constructor
    public ShowRecipes() {
        ingredients[0] = 1; //apple
        ingredients[1] = 1; //cherry
        ingredients[2] = 1;//carrot
        ingredients[3] = 0;//flour
        ingredients[4] = 0;//sugar
    }

    //overloaded constructor that covers each field
    public ShowRecipes(int apples, int cherries, int carrots, int flour, int sugar) {
        System.out.println("overloaded");
        setApples(apples);
        setCherries(cherries);
        setCarrots(carrots);
        setFlour(flour);
        setSugar(sugar);
    }

    public void input() { // user inputs ingredient counts

        //Asks user for ingredients on-hand
        System.out.println("Please enter how many of each ingredient you have:");

        System.out.print(ingredientsNames[0]);
        int apples = kbd.nextInt();
        setApples(apples);

        System.out.print(ingredientsNames[1]);
        int cherries = kbd.nextInt();
        setCherries(cherries);

        System.out.print(ingredientsNames[2]);
        int carrots = kbd.nextInt();
        setCarrots(carrots);

        System.out.print(ingredientsNames[3]);
        int flour = kbd.nextInt();
        setFlour(flour);

        System.out.print(ingredientsNames[4]);
        int sugar = kbd.nextInt();
        setSugar(sugar);
    }//end user ingredient input

    public void IngredientCount() { //Lists ingredient input counts
        System.out.println("According to your input, you have:");

        System.out.println("Ingredient\tValue");
        for(int counter=0;counter<ingredients.length;counter  ) {
            System.out.println(ingredientsNames[counter]   "\t\t"   ingredients[counter]);
        }//end for loop
        }//end ingredient listing
    
    
    public void Selection(){ //Can access every method in class except CookAgain
        System.out.println("What would you like to do now? Enter a number to select. \n1. Cook something \n2. See ingredients \n3. See available recipes \n4. Modify ingredient amounts");
        
        //1. Cook something -- WantToCook()
        //2. See ingredient list -- IngredientCount()
        //3. See available recipes -- compareRecipe()
        //4. Modify total ingredient amounts -- input()
        //maybe  put this in ShowRecipes, create object for each to put in own classes
        
        int selection = kbd.nextInt();
        
        switch (selection) {
        case 1:
            WantToCook();
            break;
        case 2: 
            IngredientCount();
            break;
        case 3: 
            compareRecipe();
            break;
        case 4:
            input();
            break;
        default:
            System.out.println("Invalid");
            return;
        }}
    
    //Setters - set from input()
    //Apple setter
    public void setApples(int apples){
        ingredients[0] = apples;
    }
    //Cherry setter
    public void setCherries(int cherries){
        ingredients[1] = cherries;
    }
    //Carrot setter
    public void setCarrots(int carrots){
        ingredients[2]= carrots;
    }
    //Flour setter
    public void setFlour(int flour){
        ingredients[3] = flour;
    }
    //Sugar setter
    public void setSugar(int sugar){
        ingredients[4] = sugar;
    }
    
    //Getters
    //Apple getter
    public int getApples(){
        return ingredients[0];
    }
    //Cherry getter
    public int getCherries(){
        return ingredients[1];
    }
    //Carrot getter
    public int getCarrots(){
        return ingredients[2];
    }
    //Flour getter
    public int getFlour(){
        return ingredients[3];
    }
    //Sugar getter
    public int getSugar(){
        return ingredients[4];
    }

    public void compareRecipe() {

        System.out.println("According to the input, you can make:");

        //Apples
        if (ingredients[0] >= 3) {
            System.out.println(RecipeNamesA[0]); //Apple Jam
        }
        if (ingredients[0] >= 2) {
            System.out.println(RecipeNamesA[1]); //Apple Jelly
            System.out.println(RecipeNamesA[2]); //Apple Smoothie
        }

        if (ingredients[0] >= 1 && ingredients[3] >= 1){
            System.out.println(RecipeNamesA[3]); //Apple Tart
        }

        if (ingredients[0] >= 2 && ingredients[4] >= 2 && ingredients[3] >= 1){
            System.out.println(RecipeNamesA[4]); //Apple Pie
        }
        if (ingredients[0] == 0) {
            System.out.println("No apple recipes");
        }

        //Cherries
        if (ingredients[1] >= 3) {
            System.out.println(RecipeNamesCh[0]); //Cherry Jam
        }
        if (ingredients[1] >= 2) {
            System.out.println(RecipeNamesCh[1]); //Cherry Jelly
        }

        if (ingredients[1] >= 2 && ingredients[3] >= 3 && ingredients[4] >= 2) {
            System.out.println(RecipeNamesCh[2]); //Cherry Pie
        }
        if (ingredients[1] >= 1 && ingredients[3] <= 1 && ingredients[4] >= 1) {
            System.out.println(RecipeNamesCh[3]); //Cherry Tart
        }
        if (ingredients[1] == 0) {
            System.out.println("No cherry recipes");
        }

        //Carrots
        if (ingredients[2] >= 2) {
            System.out.println(RecipeNamesCa[0]); //Carrot juice 
        }
        if (ingredients[2] >= 2 && ingredients[3] >= 1) {
            System.out.println(RecipeNamesCa[2]); //Carrot potage
        }

        if (ingredients[2] >= 1 && ingredients[3] >= 1 && ingredients[4] >= 1){
            System.out.println(RecipeNamesCa[1]); //Carrot cake
        }   
        if (ingredients[2] == 0) {
            System.out.println("No carrot recipes");
        }
        
    }

    private String RecipeNamesA[] = {"1. Apple Jam", "2. Apple Jelly", "3. Apple Smoothie", "4. Apple Tart", "5. Apple Pie"}; //Apple recipes
    private String RecipeNamesCh[] = {"6. Cherry Jam", "7. Cherry Jelly", "8. Cherry Pie", "9. Cherry Tart"}; //Cherry recipes
    private String RecipeNamesCa[] = {"10. Carrot Juice", "11. Carrot Cake","12. Carrot Potage"}; //Carrot recipes

    public void WantToCook() { //Enter recipe number - deducts ingredients
        
        System.out.println("What would you like to cook? Please enter the recipe number.");
        int recipeNum = kbd.nextInt();

        switch(recipeNum) {

        case 1: //Apple jam
            setApples(getApples()-3);
            System.out.println(RecipeNamesA[0]);
            break;
        case 2: //Apple jelly
            System.out.println(RecipeNamesA[1]);
            setApples(getApples()-2);
            break;
        case 3://Apple Smoothie
            System.out.println(RecipeNamesA[2]);
            setApples(getApples()-2);
            break;
        case 4: //Apple Tart
            System.out.println(RecipeNamesA[3]);
            setApples(getApples()-1);
            setFlour(getFlour()-1);
            setSugar(getSugar()-1);
            break;
        case 5: //Apple Pie
            System.out.println(RecipeNamesA[4]);
            setApples(getApples()-2);
            setFlour(getFlour()-3);
            setSugar(getSugar()-2);
            break;
        case 6: //Cherry Jam
            System.out.println(RecipeNamesCh[0]);
            setCherries(getCherries()-3);
            break;
        case 7: //Cherry Jelly
            System.out.println(RecipeNamesCh[1]);
            setCherries(getCherries()-2);
            break;
        case 8: //Cherry Pie
            System.out.println(RecipeNamesCh[2]);
            setCherries(getCherries()-2);
            setFlour(getFlour()-3);
            setSugar(getSugar()-2);
            break;
        case 9: //Cherry Tart
            System.out.println(RecipeNamesCh[3]);
            setCherries(getCherries()-1);
            setFlour(getFlour()-1);
            setSugar(getSugar()-1);
            break;
            
        case 10: //Carrot juice 
            System.out.println(RecipeNamesCa[0]);
            setCarrots(getCarrots()-2);
            break;
        case 11: //Carrot cake
            System.out.println(RecipeNamesCa[1]);
            setCarrots(getCarrots()-1);
            setFlour(getFlour()-1);
            setSugar(getSugar()-1);
            break;

        case 12: //Carrot potage
            System.out.println((RecipeNamesCa[2]));
            setCarrots(getCarrots()-2);
            setFlour(getFlour()-1); 
            break;
            
        default:
            System.out.println("Invalid entry.");
        }//End recipeNum switch
        
        System.out.println("You have cooked a thing! Good job!");
        CookAgain(); //cook again? prompt
    } //end WantToCook method

public void CookAgain() {   
    
System.out.println("Would you like to cook again? Please type yes or no");
    kbd.nextLine();
    String answer = kbd.nextLine();

    switch (answer) {
    
    case "yes":
    if (ingredients[0] >= 1 || ingredients[1] >= 1 || ingredients[2] >= 1) { //no apples, cherries, or carrots
        System.out.println("You've used some ingredients. Let's see what you have now.");
        IngredientCount();
        compareRecipe();
        WantToCook();
        break;
    }
    else {
        System.out.println("You don't have enough ingredients left to cook anything.");
        break;
    }
    case "no":
    System.out.println("Ok, done cooking. Enjoy!");
    break;
    default:
        System.out.println("Invalid answer please try again!");
    
    }// End answer switch
    
    kbd.close(); // close scanner
}
}

Here is by itty bitty driver:

import java.util.Scanner;

public class DoTheThing {
    
    public static void main (String [] args){
        final ShowRecipes first = new ShowRecipes();
        System.out.println("FIRST");
        first.input();
        first.compareRecipe();
        first.WantToCook();
        
        first.Selection();
        
    }
    
}

CodePudding user response:

PART 1 -- this answer is too long, so splitting it up amongst multiple answers

I'll start off by saying well done - this is a well designed program. It's certainly not perfect, but it definitely performs most of the basic functionality it needs to just fine.

That said, there's a few specific reasons why you are having trouble separating this into different classes.

  1. This program is tightly coupled. @MadProgrammer actually hinted towards this in the comments earlier, but basically, tightly coupled means that each subcomponent of your program is trying to do too many different things at once. As a result, if you try and change a subcomponent, it breaks other subcomponents further downstream. That makes refactoring painful and difficult. Keep your code loosely coupled, and make it so that each class only does what it absolutely needs to. The rest can be delegated to another class or method or whatever.

  2. You use a lot of global state. This isn't necessarily a bad thing, but it does make it harder to keep track of who edits what. Too much global state means breaking apart the class tends to cause logic errors. We'll be minimizing that in the examples below.

  3. Your methods are quite large and complex. This is kind of a repeat of what was said earlier, but I want to highlight this specifically - if a method gets too cumbersome (either through size, lack of readability, or complexity), it becomes exponentially more difficult to maintain. Once a method reaches some arbitrary difficulty, doing any maintenance (such as breaking apart into classes) you will find that you have dug yourself into a ditch that is quite difficult to climb out of - without doing a full rewrite of your code.

  4. This specific problem you are writing code for is practically begging to use Java Enums. Java ( JVM languages) has the most powerful implementation of enums of any language. I'm not saying enums can't be overused, but it's a really useful tool that you should work hard to get comfortable with, especially if you plan to code in Java or some JVM language in the future. And doubly so if you ever solve a problem that has a very small and specific number of entities that won't change very often. For example, your ingredients - Apple, Carrot, etc. That really should be an enum. And your recipes, those too.

I will apply each step incrementally, so that you can see the solution evolve over time.

First, here is your unchanged class.

import java.util.Scanner;

public interface Round0
{

//object class
   public class ShowRecipes {
   
      public static void main (String [] args){
         final ShowRecipes first = new ShowRecipes();
         System.out.println("FIRST");
         first.input();
         first.compareRecipe();
         first.WantToCook();
        
         first.Selection();
        
      }
    
      Scanner kbd = new Scanner(System.in);
   
    //ingredient names array
      public String ingredientsNames[] = {"Apples", "Cherry", "Carrot", "Flour ", "Sugar "};
   
    //ingredients array - setters will set
      public int[] ingredients = new int[5];
   
    //Basic constructor
      public ShowRecipes() {
         ingredients[0] = 1; //apple
         ingredients[1] = 1; //cherry
         ingredients[2] = 1;//carrot
         ingredients[3] = 0;//flour
         ingredients[4] = 0;//sugar
      }
   
    //overloaded constructor that covers each field
      public ShowRecipes(int apples, int cherries, int carrots, int flour, int sugar) {
         System.out.println("overloaded");
         setApples(apples);
         setCherries(cherries);
         setCarrots(carrots);
         setFlour(flour);
         setSugar(sugar);
      }
   
      public void input() { // user inputs ingredient counts
      
        //Asks user for ingredients on-hand
         System.out.println("Please enter how many of each ingredient you have:");
      
         System.out.print(ingredientsNames[0]);
         int apples = kbd.nextInt();
         setApples(apples);
      
         System.out.print(ingredientsNames[1]);
         int cherries = kbd.nextInt();
         setCherries(cherries);
      
         System.out.print(ingredientsNames[2]);
         int carrots = kbd.nextInt();
         setCarrots(carrots);
      
         System.out.print(ingredientsNames[3]);
         int flour = kbd.nextInt();
         setFlour(flour);
      
         System.out.print(ingredientsNames[4]);
         int sugar = kbd.nextInt();
         setSugar(sugar);
      }//end user ingredient input
   
      public void IngredientCount() { //Lists ingredient input counts
         System.out.println("According to your input, you have:");
      
         System.out.println("Ingredient\tValue");
         for(int counter=0;counter<ingredients.length;counter  ) {
            System.out.println(ingredientsNames[counter]   "\t\t"   ingredients[counter]);
         }//end for loop
      }//end ingredient listing
    
    
      public void Selection(){ //Can access every method in class except CookAgain
         System.out.println("What would you like to do now? Enter a number to select. \n1. Cook something \n2. See ingredients \n3. See available recipes \n4. Modify ingredient amounts");
        
        //1. Cook something -- WantToCook()
        //2. See ingredient list -- IngredientCount()
        //3. See available recipes -- compareRecipe()
        //4. Modify total ingredient amounts -- input()
        //maybe  put this in ShowRecipes, create object for each to put in own classes
        
         int selection = kbd.nextInt();
        
         switch (selection) {
            case 1:
               WantToCook();
               break;
            case 2: 
               IngredientCount();
               break;
            case 3: 
               compareRecipe();
               break;
            case 4:
               input();
               break;
            default:
               System.out.println("Invalid");
               return;
         }}
    
    //Setters - set from input()
    //Apple setter
      public void setApples(int apples){
         ingredients[0] = apples;
      }
    //Cherry setter
      public void setCherries(int cherries){
         ingredients[1] = cherries;
      }
    //Carrot setter
      public void setCarrots(int carrots){
         ingredients[2]= carrots;
      }
    //Flour setter
      public void setFlour(int flour){
         ingredients[3] = flour;
      }
    //Sugar setter
      public void setSugar(int sugar){
         ingredients[4] = sugar;
      }
    
    //Getters
    //Apple getter
      public int getApples(){
         return ingredients[0];
      }
    //Cherry getter
      public int getCherries(){
         return ingredients[1];
      }
    //Carrot getter
      public int getCarrots(){
         return ingredients[2];
      }
    //Flour getter
      public int getFlour(){
         return ingredients[3];
      }
    //Sugar getter
      public int getSugar(){
         return ingredients[4];
      }
   
      public void compareRecipe() {
      
         System.out.println("According to the input, you can make:");
      
        //Apples
         if (ingredients[0] >= 3) {
            System.out.println(RecipeNamesA[0]); //Apple Jam
         }
         if (ingredients[0] >= 2) {
            System.out.println(RecipeNamesA[1]); //Apple Jelly
            System.out.println(RecipeNamesA[2]); //Apple Smoothie
         }
      
         if (ingredients[0] >= 1 && ingredients[3] >= 1){
            System.out.println(RecipeNamesA[3]); //Apple Tart
         }
      
         if (ingredients[0] >= 2 && ingredients[4] >= 2 && ingredients[3] >= 1){
            System.out.println(RecipeNamesA[4]); //Apple Pie
         }
         if (ingredients[0] == 0) {
            System.out.println("No apple recipes");
         }
      
        //Cherries
         if (ingredients[1] >= 3) {
            System.out.println(RecipeNamesCh[0]); //Cherry Jam
         }
         if (ingredients[1] >= 2) {
            System.out.println(RecipeNamesCh[1]); //Cherry Jelly
         }
      
         if (ingredients[1] >= 2 && ingredients[3] >= 3 && ingredients[4] >= 2) {
            System.out.println(RecipeNamesCh[2]); //Cherry Pie
         }
         if (ingredients[1] >= 1 && ingredients[3] <= 1 && ingredients[4] >= 1) {
            System.out.println(RecipeNamesCh[3]); //Cherry Tart
         }
         if (ingredients[1] == 0) {
            System.out.println("No cherry recipes");
         }
      
        //Carrots
         if (ingredients[2] >= 2) {
            System.out.println(RecipeNamesCa[0]); //Carrot juice 
         }
         if (ingredients[2] >= 2 && ingredients[3] >= 1) {
            System.out.println(RecipeNamesCa[2]); //Carrot potage
         }
      
         if (ingredients[2] >= 1 && ingredients[3] >= 1 && ingredients[4] >= 1){
            System.out.println(RecipeNamesCa[1]); //Carrot cake
         }   
         if (ingredients[2] == 0) {
            System.out.println("No carrot recipes");
         }
        
      }
   
      private String RecipeNamesA[] = {"1. Apple Jam", "2. Apple Jelly", "3. Apple Smoothie", "4. Apple Tart", "5. Apple Pie"}; //Apple recipes
      private String RecipeNamesCh[] = {"6. Cherry Jam", "7. Cherry Jelly", "8. Cherry Pie", "9. Cherry Tart"}; //Cherry recipes
      private String RecipeNamesCa[] = {"10. Carrot Juice", "11. Carrot Cake","12. Carrot Potage"}; //Carrot recipes
   
      public void WantToCook() { //Enter recipe number - deducts ingredients
        
         System.out.println("What would you like to cook? Please enter the recipe number.");
         int recipeNum = kbd.nextInt();
      
         switch(recipeNum) {
         
            case 1: //Apple jam
               setApples(getApples()-3);
               System.out.println(RecipeNamesA[0]);
               break;
            case 2: //Apple jelly
               System.out.println(RecipeNamesA[1]);
               setApples(getApples()-2);
               break;
            case 3://Apple Smoothie
               System.out.println(RecipeNamesA[2]);
               setApples(getApples()-2);
               break;
            case 4: //Apple Tart
               System.out.println(RecipeNamesA[3]);
               setApples(getApples()-1);
               setFlour(getFlour()-1);
               setSugar(getSugar()-1);
               break;
            case 5: //Apple Pie
               System.out.println(RecipeNamesA[4]);
               setApples(getApples()-2);
               setFlour(getFlour()-3);
               setSugar(getSugar()-2);
               break;
            case 6: //Cherry Jam
               System.out.println(RecipeNamesCh[0]);
               setCherries(getCherries()-3);
               break;
            case 7: //Cherry Jelly
               System.out.println(RecipeNamesCh[1]);
               setCherries(getCherries()-2);
               break;
            case 8: //Cherry Pie
               System.out.println(RecipeNamesCh[2]);
               setCherries(getCherries()-2);
               setFlour(getFlour()-3);
               setSugar(getSugar()-2);
               break;
            case 9: //Cherry Tart
               System.out.println(RecipeNamesCh[3]);
               setCherries(getCherries()-1);
               setFlour(getFlour()-1);
               setSugar(getSugar()-1);
               break;
            
            case 10: //Carrot juice 
               System.out.println(RecipeNamesCa[0]);
               setCarrots(getCarrots()-2);
               break;
            case 11: //Carrot cake
               System.out.println(RecipeNamesCa[1]);
               setCarrots(getCarrots()-1);
               setFlour(getFlour()-1);
               setSugar(getSugar()-1);
               break;
         
            case 12: //Carrot potage
               System.out.println((RecipeNamesCa[2]));
               setCarrots(getCarrots()-2);
               setFlour(getFlour()-1); 
               break;
            
            default:
               System.out.println("Invalid entry.");
         }//End recipeNum switch
        
         System.out.println("You have cooked a thing! Good job!");
         CookAgain(); //cook again? prompt
      } //end WantToCook method
   
      public void CookAgain() {   
      
         System.out.println("Would you like to cook again? Please type yes or no");
         kbd.nextLine();
         String answer = kbd.nextLine();
      
         switch (answer) {
         
            case "yes":
               if (ingredients[0] >= 1 || ingredients[1] >= 1 || ingredients[2] >= 1) { //no apples, cherries, or carrots
                  System.out.println("You've used some ingredients. Let's see what you have now.");
                  IngredientCount();
                  compareRecipe();
                  WantToCook();
                  break;
               }
               else {
                  System.out.println("You don't have enough ingredients left to cook anything.");
                  break;
               }
            case "no":
               System.out.println("Ok, done cooking. Enjoy!");
               break;
            default:
               System.out.println("Invalid answer please try again!");
         
         }// End answer switch
      
         kbd.close(); // close scanner
      }
   }

}

Let's start by fixing #1

#1 Tight Coupling

Right now, your ShowRecipes class literally does it all.

  • A - It communicates with your user
  • B - It handles the business logic, which is the logic decides what should change and when.
  • C - It manipulates the internal state -- the act of actually changing stuff, as opposed to deciding when to change. For example, recognizing that you need to subtract 5 apples, as opposed to actually doing the subtraction

That is a lot for one class to handle. So the first thing I would do is decouple each of its methods into multiple smaller ones. Then after that, we can start grouping methods into different classes. Let's look through your methods.

  • public ShowRecipes() - This is nice and simple, almost exactly what we are looking for. All it does is change state. This method shouldn't be modified (yet). And while I'm at it, I feel similarly about public ShowRecipes(int apples, int cherries, int carrots, int flour, int sugar)

  • public void input() - This isn't bad. As is, your method does 3 things - prints a message to a user, reads their response, then changes state to equal the users response. However, the part that makes this method (and thus, the class) tightly coupled is that it hardcodes the implementation inside your method. For example, you are using System.out.println() to print all your stuff to the commandline. Let's replace all of those with calls in input() with a new class that handles the talking with the user - UserPrompt. This class will ask a question, then return the answer the user gave.

   public class UserPrompt
   {
   
      final Scanner kbd = new Scanner(System.in);
   
      public int askForNumber(String prompt)
      {
      
         System.out.print(prompt);
         
         return kbd.nextInt();
         
      }
   
   }

Then, we will rework your input method to use the UserPrompt, but this time, will we make it LOOSELY coupled by passing in an instance of UserPrompt through the parameter of input().

      public void input(UserPrompt prompt) { // user inputs ingredient counts
      
        //Asks user for ingredients on-hand
         System.out.println("Please enter how many of each ingredient you have:");
      
         setApples(prompt.askForNumber(ingredientsNames[0]));
      
         setCherries(prompt.askForNumber(ingredientsNames[1]));
      
         setCarrots(prompt.askForNumber(ingredientsNames[2]));
      
         setFlour(prompt.askForNumber(ingredientsNames[3]));
      
         setSugar(prompt.askForNumber(ingredientsNames[4]));
      }//end user ingredient input

And for your main method, change it to be this.

public static void main (String [] args){

      final ShowRecipes first = new ShowRecipes();
      System.out.println("FIRST");

      final UserPrompt prompt = new UserPrompt();

      first.input(prompt);
      first.compareRecipe();
      first.WantToCook();
        
      first.Selection();
        
   }

Now, if you try compiling this, you might get an error in your Selection method. I am going to jump ahead just a little bit and modify Selection as well.

public void Selection(UserPrompt prompt){
         
            //I left out some lines that used to be here, just to keep things simple
            //Those lines we don't care about right now
            //just leave them alone, and only change the parameter line above
            //and the input line below

            case 4:
               input(prompt);
               break;
            default:
               System.out.println("Invalid");
               return;
         }}

Then, go back to your main method, and change only the first.Selection() line.

   public static void main (String [] args){
      //ignore the other lines, only change this line below
      first.Selection(prompt);
        
   }

If you compile your code now, there should be no errors. And if you run it, it should work exactly the same way it used to.

Now, what benefit did this give us? Well for starters, now we have simplified the logic in our input() method. It may be a bit hard to read because there's 3 methods in one line, so that's a negative. But on the plus side, we are more loosely coupled. That helps us in a big way.

For example, your program has a minor formatting error. In the beginning, when you ask your user how many of each ingredient they want, your prompt comes out looking like this.

Please enter how many of each ingredient you have:
Apples1
Cherry2
Carrot3
Flour 4
Sugar 5

The number is smushed together with the word. If you wanted to clean that up using your old way, you would have to punch in a whole bunch of spaces like this.

   public void input() { // user inputs ingredient counts
   
        //Asks user for ingredients on-hand
      System.out.println("Please enter how many of each ingredient you have:");
   
      System.out.print(ingredientsNames[0]   " - "); //cleaned up now
      int apples = kbd.nextInt();
      setApples(apples);
   
      System.out.print(ingredientsNames[1]   " - "); //but you must add it
      int cherries = kbd.nextInt();
      setCherries(cherries);
   
      System.out.print(ingredientsNames[2]   " - "); //to each one of these
      int carrots = kbd.nextInt();
      setCarrots(carrots);
   
      System.out.print(ingredientsNames[3]   " - "); //lines, which makes 
      int flour = kbd.nextInt();
      setFlour(flour);
   
      System.out.print(ingredientsNames[4]   " - "); //minor changes tedious
      int sugar = kbd.nextInt();
      setSugar(sugar);
   }//end user ingredient input

Your output looks nice and pretty now -- Apples - 5, but you needed to change 5 things. And if you ever want to use an = instead of a -, you'll need to make another 5 changes. And if you prefer spaces instead of a symbol, another 5 changes. And 5 is only because you have 5 ingredients. What if you had 50? That would be boring, tedious, and error-prone - making many repetitive changes is a great to accidentally mistype and cause a bug in your code.

But using the new way, you only need to make one tiny change in your UserPrompt class.

   public class UserPrompt
   {
   
      final Scanner kbd = new Scanner(System.in);
   
      public int askForNumber(String prompt)
      {
      
         //Print your prompt to the command line -- I added a little extra at the end for simple reading
         System.out.print(prompt   " - ");
         
         return kbd.nextInt();
         
      }
   

Now, all of your output looks clean, and if you ever want to change the way the output looks, you only need to make one change in UserPrompt. And to be clear, the reason why this code is now loosely coupled is because you used a parameter to decide how the reading and writing goes, then used that parameter for the rest of your method. Since you are plugging in your reader/writer in as a parameter and using it in the rest of your method, that means you only need to change the parameter in order to change the whole method. And that is what loose coupling means - your components are plug-and-play, allowing you to change things quickly, without requiring you to make many changes elsewhere. All you had to do was plug in a parameter into the method, then define it in your main (and Selection).

That was a difficult topic, but it's a massive part of Software Design. And this is likely the benefits that @MadProgrammer was hinting towards when suggesting this idea to you in the comments. On to the next method.

  • public void IngredientCount() - So as we mentioned earlier, tight coupling means limiting each class to doing as little as possible, and using plug and play to handle any extra functionality we may need. We printed and read stuff from the screen using UserPrompt, so it's not too much of a stretch to give UserPrompt the ability to just print. Like this.
      
   public class UserPrompt
   {
      //ignore all the lines from the askForNumber method
      //just add this method below that method, but still in UserPrompt
      public void print(String message)
      {
      
         System.out.println(message);
      
      }
   
   }

Then, change the original method.

      public void IngredientCount(UserPrompt prompt) { //Lists ingredient input counts
         prompt.print("According to your input, you have:");
      
         prompt.print("Ingredient\tValue");
         for(int counter=0;counter<ingredients.length;counter  ) {
            prompt.print(ingredientsNames[counter]   "\t\t"   ingredients[counter]);
         }//end for loop
      }//end ingredient listing

Now, instead of using System.out.println() to print, we can use UserPrompt and use it's print() method to print. As you can see, you use a lot of System.out.println() in your program. I'll take the liberty of going ahead and replacing all of those with the print() method in UserPrompt. I will also replace all of your prompts (how many apples?) with the askForNumber() method in UserPrompt.

This is a very long, repetitive, and tedious change to make - and not something a beginner should waste their time doing. Frankly, you probably understand the concept at this point, so I will just do it for you. Furthermore, we are going to do this for the rest of the methods, so let me just knock them all out now in one fell swoop.

UNFORTUNATELY, I HAVE RUN OUT OF CHARACTERS, SO THIS WILL BE PART 1 of multiple.

CodePudding user response:

PART 2

Ok, I have now replaced all System.out.println() in your program to use the print() method from UserPrompt. Here it is.

import java.util.Scanner;

public interface Round1
{


   public static void main (String [] args){
      final ShowRecipes first = new ShowRecipes();
      System.out.println("FIRST");
      
      final UserPrompt prompt = new UserPrompt();
      
      first.input(prompt);
      first.compareRecipe(prompt);
      first.WantToCook(prompt);
        
      first.Selection(prompt);
        
   }
      
   public class UserPrompt
   {
   
      final Scanner kbd = new Scanner(System.in);
   
      public int askForNumber(String prompt)
      {
      
         //Print your prompt to the command line -- I added a little extra at the end for simple reading
         System.out.print(prompt   " - ");
         
         return kbd.nextInt();
         
      }
      
      public void print(String message)
      {
      
         System.out.println(message);
      
      }
   
   }

//object class
   public class ShowRecipes {
   
    
      Scanner kbd = new Scanner(System.in);
   
    //ingredient names array
      public String ingredientsNames[] = {"Apples", "Cherry", "Carrot", "Flour ", "Sugar "};
   
    //ingredients array - setters will set
      public int[] ingredients = new int[5];
   
    //Basic constructor
      public ShowRecipes() {
         ingredients[0] = 1; //apple
         ingredients[1] = 1; //cherry
         ingredients[2] = 1;//carrot
         ingredients[3] = 0;//flour
         ingredients[4] = 0;//sugar
      }
   
    //overloaded constructor that covers each field
      public ShowRecipes(int apples, int cherries, int carrots, int flour, int sugar) {
         System.out.println("overloaded");
         setApples(apples);
         setCherries(cherries);
         setCarrots(carrots);
         setFlour(flour);
         setSugar(sugar);
      }
   
      public void input(UserPrompt prompt) { // user inputs ingredient counts
      
        //Asks user for ingredients on-hand
         prompt.print("Please enter how many of each ingredient you have:");
      
         setApples(prompt.askForNumber(ingredientsNames[0]));
      
         setCherries(prompt.askForNumber(ingredientsNames[1]));
      
         setCarrots(prompt.askForNumber(ingredientsNames[2]));
      
         setFlour(prompt.askForNumber(ingredientsNames[3]));
      
         setSugar(prompt.askForNumber(ingredientsNames[4]));
      }//end user ingredient input
   
      public void IngredientCount(UserPrompt prompt) { //Lists ingredient input counts
         prompt.print("According to your input, you have:");
      
         prompt.print("Ingredient\tValue");
         for(int counter=0;counter<ingredients.length;counter  ) {
            prompt.print(ingredientsNames[counter]   "\t\t"   ingredients[counter]);
         }//end for loop
      }//end ingredient listing
    
    
      public void Selection(UserPrompt prompt){ //Can access every method in class except CookAgain
        
        //1. Cook something -- WantToCook()
        //2. See ingredient list -- IngredientCount()
        //3. See available recipes -- compareRecipe()
        //4. Modify total ingredient amounts -- input()
        //maybe  put this in ShowRecipes, create object for each to put in own classes
        
         int userChoice = 
            prompt.askForNumber(
               "What would you like to do now? Enter a number to select. \n1. Cook something \n2. See ingredients \n3. See available recipes \n4. Modify ingredient amounts"
            );
        
         switch (userChoice) {
            case 1:
               WantToCook(prompt);
               break;
            case 2: 
               IngredientCount(prompt);
               break;
            case 3: 
               compareRecipe(prompt);
               break;
            case 4:
               input(prompt);
               break;
            default:
               System.out.println("Invalid");
               return;
         }}
    
    //Setters - set from input()
    //Apple setter
      public void setApples(int apples){
         ingredients[0] = apples;
      }
    //Cherry setter
      public void setCherries(int cherries){
         ingredients[1] = cherries;
      }
    //Carrot setter
      public void setCarrots(int carrots){
         ingredients[2]= carrots;
      }
    //Flour setter
      public void setFlour(int flour){
         ingredients[3] = flour;
      }
    //Sugar setter
      public void setSugar(int sugar){
         ingredients[4] = sugar;
      }
    
    //Getters
    //Apple getter
      public int getApples(){
         return ingredients[0];
      }
    //Cherry getter
      public int getCherries(){
         return ingredients[1];
      }
    //Carrot getter
      public int getCarrots(){
         return ingredients[2];
      }
    //Flour getter
      public int getFlour(){
         return ingredients[3];
      }
    //Sugar getter
      public int getSugar(){
         return ingredients[4];
      }
   
      public void compareRecipe(UserPrompt prompt) {
      
         prompt.print("According to the input, you can make:");
      
        //Apples
         if (ingredients[0] >= 3) {
            prompt.print(RecipeNamesA[0]); //Apple Jam
         }
         if (ingredients[0] >= 2) {
            prompt.print(RecipeNamesA[1]); //Apple Jelly
            prompt.print(RecipeNamesA[2]); //Apple Smoothie
         }
      
         if (ingredients[0] >= 1 && ingredients[3] >= 1){
            prompt.print(RecipeNamesA[3]); //Apple Tart
         }
      
         if (ingredients[0] >= 2 && ingredients[4] >= 2 && ingredients[3] >= 1){
            prompt.print(RecipeNamesA[4]); //Apple Pie
         }
         if (ingredients[0] == 0) {
            prompt.print("No apple recipes");
         }
      
        //Cherries
         if (ingredients[1] >= 3) {
            prompt.print(RecipeNamesCh[0]); //Cherry Jam
         }
         if (ingredients[1] >= 2) {
            prompt.print(RecipeNamesCh[1]); //Cherry Jelly
         }
      
         if (ingredients[1] >= 2 && ingredients[3] >= 3 && ingredients[4] >= 2) {
            prompt.print(RecipeNamesCh[2]); //Cherry Pie
         }
         if (ingredients[1] >= 1 && ingredients[3] <= 1 && ingredients[4] >= 1) {
            prompt.print(RecipeNamesCh[3]); //Cherry Tart
         }
         if (ingredients[1] == 0) {
            prompt.print("No cherry recipes");
         }
      
        //Carrots
         if (ingredients[2] >= 2) {
            prompt.print(RecipeNamesCa[0]); //Carrot juice 
         }
         if (ingredients[2] >= 2 && ingredients[3] >= 1) {
            prompt.print(RecipeNamesCa[2]); //Carrot potage
         }
      
         if (ingredients[2] >= 1 && ingredients[3] >= 1 && ingredients[4] >= 1){
            prompt.print(RecipeNamesCa[1]); //Carrot cake
         }   
         if (ingredients[2] == 0) {
            prompt.print("No carrot recipes");
         }
        
      }
   
      private String RecipeNamesA[] = {"1. Apple Jam", "2. Apple Jelly", "3. Apple Smoothie", "4. Apple Tart", "5. Apple Pie"}; //Apple recipes
      private String RecipeNamesCh[] = {"6. Cherry Jam", "7. Cherry Jelly", "8. Cherry Pie", "9. Cherry Tart"}; //Cherry recipes
      private String RecipeNamesCa[] = {"10. Carrot Juice", "11. Carrot Cake","12. Carrot Potage"}; //Carrot recipes
   
      public void WantToCook(UserPrompt prompt) { //Enter recipe number - deducts ingredients
        
         int recipeNum = prompt.askForNumber("What would you like to cook? Please enter the recipe number.");
         
         switch(recipeNum) {
         
            case 1: //Apple jam
               setApples(getApples()-3);
               prompt.print(RecipeNamesA[0]);
               break;
            case 2: //Apple jelly
               prompt.print(RecipeNamesA[1]);
               setApples(getApples()-2);
               break;
            case 3://Apple Smoothie
               prompt.print(RecipeNamesA[2]);
               setApples(getApples()-2);
               break;
            case 4: //Apple Tart
               prompt.print(RecipeNamesA[3]);
               setApples(getApples()-1);
               setFlour(getFlour()-1);
               setSugar(getSugar()-1);
               break;
            case 5: //Apple Pie
               prompt.print(RecipeNamesA[4]);
               setApples(getApples()-2);
               setFlour(getFlour()-3);
               setSugar(getSugar()-2);
               break;
            case 6: //Cherry Jam
               prompt.print(RecipeNamesCh[0]);
               setCherries(getCherries()-3);
               break;
            case 7: //Cherry Jelly
               prompt.print(RecipeNamesCh[1]);
               setCherries(getCherries()-2);
               break;
            case 8: //Cherry Pie
               prompt.print(RecipeNamesCh[2]);
               setCherries(getCherries()-2);
               setFlour(getFlour()-3);
               setSugar(getSugar()-2);
               break;
            case 9: //Cherry Tart
               prompt.print(RecipeNamesCh[3]);
               setCherries(getCherries()-1);
               setFlour(getFlour()-1);
               setSugar(getSugar()-1);
               break;
            
            case 10: //Carrot juice 
               prompt.print(RecipeNamesCa[0]);
               setCarrots(getCarrots()-2);
               break;
            case 11: //Carrot cake
               prompt.print(RecipeNamesCa[1]);
               setCarrots(getCarrots()-1);
               setFlour(getFlour()-1);
               setSugar(getSugar()-1);
               break;
         
            case 12: //Carrot potage
               prompt.print((RecipeNamesCa[2]));
               setCarrots(getCarrots()-2);
               setFlour(getFlour()-1); 
               break;
            
            default:
               prompt.print("Invalid entry.");
         }//End recipeNum switch
        
         prompt.print("You have cooked a thing! Good job!");
         CookAgain(prompt); //cook again? prompt
      } //end WantToCook method
   
      public void CookAgain(UserPrompt prompt) {   
      
         System.out.print("Would you like to cook again? Please type yes or no - ");
         String answer = kbd.next();
      
         switch (answer) {
         
            case "yes":
               if (ingredients[0] >= 1 || ingredients[1] >= 1 || ingredients[2] >= 1) { //no apples, cherries, or carrots
                  prompt.print("You've used some ingredients. Let's see what you have now.");
                  IngredientCount(prompt);
                  compareRecipe(prompt);
                  WantToCook(prompt);
                  break;
               }
               else {
                  prompt.print("You don't have enough ingredients left to cook anything.");
                  break;
               }
            case "no":
               prompt.print("Ok, done cooking. Enjoy!");
               break;
            default:
               prompt.print("Invalid answer please try again!");
         
         }// End answer switch
      
         kbd.close(); // close scanner
      }
   }

}

Now, what does this actually do for us? That was a lot of change, but what is the benefit?

Well, for starters, we have removed all of the hardcoded print statements, and wrapped them all under a single method print(). Same for askForNumber().

Now, I could explain why that is powerful, or I could show it to you. Try running this new class above like normal. Then, completely replace the UserPrompt class above with this new own below.

//this time, replace the whole UserPrompt class with what is here
   public class UserPrompt
   {
   
      //final Scanner kbd = new Scanner(System.in);
   
      public int askForNumber(String prompt)
      {
      
         //System.out.print(prompt   " - ");
         //
         //return kbd.nextInt();
         
         String message = javax.swing.JOptionPane.showInputDialog(null, prompt   " - ");
      
         return Integer.parseInt(message);
         
      }
      
      public void print(String message)
      {
      
         //System.out.println(message);
         javax.swing.JOptionPane.showMessageDialog(null, message);
      
      }
   
   }

Now try running the class. You should see something very cool. Now, your application has windows and popups and buttons. It may not look very pretty, but hopefully this shows you how making your code loosely coupled instead of tightly coupled can allow some powerful abilities.

Now, change back the UserPrompt to the boring command line version, and let's continue. Last we checked, we were looking at IngredientCount and decided to replace all of the print statements with the print() method in UserPrompt. So now, let's look at the next method, Selection().

  • Selection() - Obviously, we have removed coupling with print and read, the prompt at the beginning of this method is considered a loose coupling. As for the rest of this method, it actually only does one simple thing - decides what to do based on what the user requests. On the one hand, this method does not need to be broken up at all. It is exactly as large as it needs to be and does only one thing on its own. On the other hand, this method seems like it should be in a different class based on the methods we've looked at previously. For example, in the public ShowRecipes() method, it would take in inputs and store them into state. That was purely data manipulation/changing state.

Whereas for this method, we are using the input to make branching decisions. One could say that this sounds more like a Driver class or a BusinessLogic class. I will make a class called Driver, and put this method into there.

   public class Driver
   {
   
      public void selection(UserPrompt prompt){ //Can access every method in class except CookAgain
        
        //1. Cook something -- WantToCook()
        //2. See ingredient list -- IngredientCount()
        //3. See available recipes -- compareRecipe()
        //4. Modify total ingredient amounts -- input()
        //maybe  put this in ShowRecipes, create object for each to put in own classes
        
         int userChoice = 
            prompt.askForNumber(
               "What would you like to do now? Enter a number to select. \n1. Cook something \n2. See ingredients \n3. See available recipes \n4. Modify ingredient amounts"
            );
        
         switch (userChoice) {
            case 1:
               WantToCook(prompt);
               break;
            case 2: 
               IngredientCount(prompt);
               break;
            case 3: 
               compareRecipe(prompt);
               break;
            case 4:
               input(prompt);
               break;
            default:
               System.out.println("Invalid");
               return;
         }
         
      }
       
   }

Now, immediately, several things break. The class is now trying to find all these methods that belong to ShowRecipes, but can't find them because we are now in the Driver class. To make things simple, we are actually going to do something rather drastic - we are going to copy all of those methods over into the Driver class. And the reason we are doing this is because all of these methods actually have the same sort of style of decide what to do based upon user input. In fact, let's take a quick look at each.

  1. WantToCook() - depending on what the user says, we manipulate state, then print to the screen our response. So this does 3 things - decides what to do, then manipulates state, then prints to the screen.
  2. IngredientCount() - This one reads the state, then prints it out the state to the user. So this one does 2 things - reads state, then prints it out to the user.
  3. compareRecipe() - This one reads the state, then prints it out to the user. So this does 2 things - reads state, then prints it out to the user.
  4. input() - depending on what the user says, we populate state. So this does 2 things - decides what to do, then populates state.

So, looking at these, we see 2 classes of methods - one that decides then does something, and another that reads then does something. So far, the Driver class definitely falls under the decides then does something mindset, and so does selection(), so we will bring WantToCook() and input() over to the Driver class as well.

Now your Driver class should look like this.


   public class Driver
   {
   
      public void selection(UserPrompt prompt){ //Can access every method in class except CookAgain
        
        //1. Cook something -- WantToCook()
        //2. See ingredient list -- IngredientCount()
        //3. See available recipes -- compareRecipe()
        //4. Modify total ingredient amounts -- input()
        //maybe  put this in ShowRecipes, create object for each to put in own classes
        
         int userChoice = 
            prompt.askForNumber(
               "What would you like to do now? Enter a number to select. \n1. Cook something \n2. See ingredients \n3. See available recipes \n4. Modify ingredient amounts"
            );
        
         switch (userChoice) {
            case 1:
               WantToCook(prompt);
               break;
            case 2: 
               IngredientCount(prompt);
               break;
            case 3: 
               compareRecipe(prompt);
               break;
            case 4:
               input(prompt);
               break;
            default:
               System.out.println("Invalid");
               return;
         }
         
      }
   
      public void WantToCook(UserPrompt prompt) { //Enter recipe number - deducts ingredients
        
         int recipeNum = prompt.askForNumber("What would you like to cook? Please enter the recipe number.");
         
         switch(recipeNum) {
         
            case 1: //Apple jam
               setApples(getApples()-3);
               prompt.print(RecipeNamesA[0]);
               break;
            case 2: //Apple jelly
               prompt.print(RecipeNamesA[1]);
               setApples(getApples()-2);
               break;
            case 3://Apple Smoothie
               prompt.print(RecipeNamesA[2]);
               setApples(getApples()-2);
               break;
            case 4: //Apple Tart
               prompt.print(RecipeNamesA[3]);
               setApples(getApples()-1);
               setFlour(getFlour()-1);
               setSugar(getSugar()-1);
               break;
            case 5: //Apple Pie
               prompt.print(RecipeNamesA[4]);
               setApples(getApples()-2);
               setFlour(getFlour()-3);
               setSugar(getSugar()-2);
               break;
            case 6: //Cherry Jam
               prompt.print(RecipeNamesCh[0]);
               setCherries(getCherries()-3);
               break;
            case 7: //Cherry Jelly
               prompt.print(RecipeNamesCh[1]);
               setCherries(getCherries()-2);
               break;
            case 8: //Cherry Pie
               prompt.print(RecipeNamesCh[2]);
               setCherries(getCherries()-2);
               setFlour(getFlour()-3);
               setSugar(getSugar()-2);
               break;
            case 9: //Cherry Tart
               prompt.print(RecipeNamesCh[3]);
               setCherries(getCherries()-1);
               setFlour(getFlour()-1);
               setSugar(getSugar()-1);
               break;
            
            case 10: //Carrot juice 
               prompt.print(RecipeNamesCa[0]);
               setCarrots(getCarrots()-2);
               break;
            case 11: //Carrot cake
               prompt.print(RecipeNamesCa[1]);
               setCarrots(getCarrots()-1);
               setFlour(getFlour()-1);
               setSugar(getSugar()-1);
               break;
         
            case 12: //Carrot potage
               prompt.print((RecipeNamesCa[2]));
               setCarrots(getCarrots()-2);
               setFlour(getFlour()-1); 
               break;
            
            default:
               prompt.print("Invalid entry.");
         }//End recipeNum switch
        
         prompt.print("You have cooked a thing! Good job!");
         CookAgain(prompt); //cook again? prompt
      } //end WantToCook method
   
      public void input(UserPrompt prompt) { // user inputs ingredient counts
      
        //Asks user for ingredients on-hand
         prompt.print("Please enter how many of each ingredient you have:");
      
         setApples(prompt.askForNumber(ingredientsNames[0]));
      
         setCherries(prompt.askForNumber(ingredientsNames[1]));
      
         setCarrots(prompt.askForNumber(ingredientsNames[2]));
      
         setFlour(prompt.askForNumber(ingredientsNames[3]));
      
         setSugar(prompt.askForNumber(ingredientsNames[4]));
      }//end user ingredient input
   
   }

Now, if you try compiling at this point, you will see a scary list of errors. That is because we have not finished making our changes, so it is not yet a reason to be concerned.

Now, actually looking at the errors, the first one we will see is for the main method.

Round1.java:13: error: cannot find symbol
      first.input(prompt);
           ^
  symbol:   method input(UserPrompt)
  location: variable first of type ShowRecipes

It's complaining that it can't find the method input() inside of ShowRecipes(). Well, that is because we just took that method out and placed it into the Driver class. So, we just need to update the main method to call input() on the driver instance of the Driver class.

//replace the whole method
   public static void main (String [] args){
      final ShowRecipes first = new ShowRecipes();
      System.out.println("FIRST");
      
      final UserPrompt prompt = new UserPrompt();
      
      final Driver driver = new Driver();
      driver.input(prompt);
      first.compareRecipe(prompt);
      first.WantToCook(prompt);
   
      driver.selection(prompt);
        
   }

Ok, that should have removed that one error. We will see a very similar one for the WantToCook() method. We can do the same, resulting in the method below.

   public static void main (String [] args){
      final ShowRecipes first = new ShowRecipes();
      System.out.println("FIRST");
      
      final UserPrompt prompt = new UserPrompt();
      
      final Driver driver = new Driver();
      driver.input(prompt);
      first.compareRecipe(prompt);
      driver.WantToCook(prompt);
   
      driver.selection(prompt);
        
   }

Now, there should be no more errors in our main method. Of course, there are still plenty to work through. Let's look at the next error.

Round1.java:67: error: cannot find symbol
               IngredientCount(prompt);
               ^
  symbol:   method IngredientCount(UserPrompt)
  location: class Driver

This error says that it can't find the IngredientCount() method inside of Driver. That is because the IngredientCount() method is in ShowRecipes. And spoiler alert - there's going to be more errors saying almost the exact same thing, but for methods with different names. So, to make our lives easier, let's tackle all of these errors at once. To do this, we are going to plug in our instance of ShowRecipes into the Driver class using a constructor. Again, this ties back to what @MadProgrammer was saying earlier, but this time, it is going to be done through a constructor instead of a method.

First let's modify the Driver class to include a new constructor and an instance field of ShowRecipes.

   public class Driver
   {
   
      ShowRecipes sr;
      
      public Driver(ShowRecipes parameter)
      {
      
         sr = parameter;
      
      }

   //ignore all the lines after this
   }

By doing this, we have added a small amount of state to the Driver class. Now, this might seem counter productive - didn't we create the Driver class because it wasn't supposed to make decisions and handle state? Well, yes, but there is a big difference this time. All of the state changes are going to be handled by the new ShowRecipes that we added inside of Driver. So Driver won't actually be doing any of the state changes itself, it will simply have whatever state it needs to know about be plugged in, then the Driver class can tell it to manipulate itself. Again, the keywords here a plug-in and delegate.

Now, the first error that comes up when we compile is back in the main method. This is easy to fix, just change it to be like this.

   public static void main (String [] args){
      //ignore the lines before this one
      final Driver driver = new Driver(first);
      //ignore the lines after this one
   }

Once we do this, then driver has first plugged into it, allowing Driver to delegate all state changes to ShowRecipes. Now, let's start reworking some of the methods in Driver to use it.

For example, in the selection() method earlier, it couldn't find IngredientCount() and compareRecipe(). But now that we have plugged in ShowRecipes, let's change selection() to use ShowRecipes for those methods.

      public void selection(UserPrompt prompt){
            //ignore the lines before this one
            case 2: 
               sr.IngredientCount(prompt);
               break;
            case 3: 
               sr.compareRecipe(prompt);
               break;
            //ignore the lines after this one
      }

RAN OUT OF CHARACTERS, SO CONTINUED IN PART 3

CodePudding user response:

PART 3

No more errors in selection(). Now, the next method with errors is WantToCook(). Let's do the same there. Any method that currently belongs to ShowRecipes, we will put an sr in front of it, in order to delegate that method to the ShowRecipes instance that the Driver class has inside of it.

      public void WantToCook(UserPrompt prompt) { //Enter recipe number - deducts ingredients
        
         int recipeNum = prompt.askForNumber("What would you like to cook? Please enter the recipe number.");
         
         switch(recipeNum) {
         
            case 1: //Apple jam
               sr.setApples(sr.getApples()-3);
               prompt.print(sr.RecipeNamesA[0]);
               break;
            case 2: //Apple jelly
               prompt.print(sr.RecipeNamesA[1]);
               sr.setApples(sr.getApples()-2);
               break;
            case 3://Apple Smoothie
               prompt.print(sr.RecipeNamesA[2]);
               sr.setApples(sr.getApples()-2);
               break;
            case 4: //Apple Tart
               prompt.print(sr.RecipeNamesA[3]);
               sr.setApples(sr.getApples()-1);
               sr.setFlour(sr.getFlour()-1);
               sr.setSugar(sr.getSugar()-1);
               break;
            case 5: //Apple Pie
               prompt.print(sr.RecipeNamesA[4]);
               sr.setApples(sr.getApples()-2);
               sr.setFlour(sr.getFlour()-3);
               sr.setSugar(sr.getSugar()-2);
               break;
            case 6: //Cherry Jam
               prompt.print(sr.RecipeNamesCh[0]);
               sr.setCherries(sr.getCherries()-3);
               break;
            case 7: //Cherry Jelly
               prompt.print(sr.RecipeNamesCh[1]);
               sr.setCherries(sr.getCherries()-2);
               break;
            case 8: //Cherry Pie
               prompt.print(sr.RecipeNamesCh[2]);
               sr.setCherries(sr.getCherries()-2);
               sr.setFlour(sr.getFlour()-3);
               sr.setSugar(sr.getSugar()-2);
               break;
            case 9: //Cherry Tart
               prompt.print(sr.RecipeNamesCh[3]);
               sr.setCherries(sr.getCherries()-1);
               sr.setFlour(sr.getFlour()-1);
               sr.setSugar(sr.getSugar()-1);
               break;
            
            case 10: //Carrot juice 
               prompt.print(sr.RecipeNamesCa[0]);
               sr.setCarrots(sr.getCarrots()-2);
               break;
            case 11: //Carrot cake
               prompt.print(sr.RecipeNamesCa[1]);
               sr.setCarrots(sr.getCarrots()-1);
               sr.setFlour(sr.getFlour()-1);
               sr.setSugar(sr.getSugar()-1);
               break;
         
            case 12: //Carrot potage
               prompt.print((sr.RecipeNamesCa[2]));
               sr.setCarrots(sr.getCarrots()-2);
               sr.setFlour(sr.getFlour()-1); 
               break;
            
            default:
               prompt.print("Invalid entry.");
         }//End recipeNum switch
        
         prompt.print("You have cooked a thing! Good job!");
         sr.CookAgain(prompt); //cook again? prompt
      } //end WantToCook method

Pretty long winded, but this eliminates all the errors in the WantToCook() method.

Now the next error is in input(). It is the exact same type of problem, and thus, we solve it the same way.

      public void input(UserPrompt prompt) { // user inputs ingredient counts
      
        //Asks user for ingredients on-hand
         prompt.print("Please enter how many of each ingredient you have:");
      
         sr.setApples(prompt.askForNumber(sr.ingredientsNames[0]));
      
         sr.setCherries(prompt.askForNumber(sr.ingredientsNames[1]));
      
         sr.setCarrots(prompt.askForNumber(sr.ingredientsNames[2]));
      
         sr.setFlour(prompt.askForNumber(sr.ingredientsNames[3]));
      
         sr.setSugar(prompt.askForNumber(sr.ingredientsNames[4]));
      }//end user ingredient input

And now, all the errors in Driver are gone. But if we compile, now we have some errors in ShowRecipes.

Round1.java:345: error: cannot find symbol
                  WantToCook(prompt);
                  ^
  symbol:   method WantToCook(UserPrompt)
  location: class ShowRecipes

This error is saying that it cannot find WantToCook() inside of the ShowRecipes method called CookAgain().

Now, WantToCook() exists in Driver. We just moved it there because we felt that it was handling a Driver-like, decision-making problem rather than the other methods in ShowRecipes. However, because of that, ShowRecipes is broken because it can't find it.

You might be tempted to reference Driver inside of ShowRecipes, which is the vice versa of what we just did - putting ShowRecipes inside of Driver. However, that would not be the correct move here. And the reason why is because CookAgain() is actually a decision-making method too. We didn't move it before only because we hadn't gotten that far yet. But now that we are here, this method needs to go over to Driver because it is a decision-making based method.

I just cut and pasted the method with no change into Driver. However, once we do that, we actually need to undo a tiny bit of work that we did before in the WantToCook(). Here is the change.

      public void WantToCook(UserPrompt prompt) {
         //ignore the lines before this one
         CookAgain(prompt); //cook again? prompt
      } //end WantToCook method

Now that we have moved CookAgain into Driver, that means it no longer belongs to ShowRecipes, and by extension, sr. So, we removed the sr from this line only.

Now that that is done, we have one more error in the Driver code. Basically, it looks like there is one point in your program where, instead of asking for a number, you asked for a yes or no. For right now, just to get things working, I am going to change it to use numbers. We can change that later after things are all cleaned up.

      public void CookAgain(UserPrompt prompt) {   
      
         int answer = prompt.askForNumber("Would you like to cook again? 1 = yes, 2 = no - ");
      
         switch (answer) {
         
            case 1: //which means yes
               if (ingredients[0] >= 1 || ingredients[1] >= 1 || ingredients[2] >= 1) { //no apples, cherries, or carrots
                  prompt.print("You've used some ingredients. Let's see what you have now.");
                  IngredientCount(prompt);
                  compareRecipe(prompt);
                  WantToCook(prompt);
                  break;
               }
               else {
                  prompt.print("You don't have enough ingredients left to cook anything.");
                  break;
               }
            case 2: //which means no
               prompt.print("Ok, done cooking. Enjoy!");
               break;
            default:
               prompt.print("Invalid answer please try again!");
         
         }// End answer switch
      
      }

Now that that is done, we can go back to what we were doing before - delegating methods to sr that belong to sr. Here are those changes.

      public void CookAgain(UserPrompt prompt) {   
      
         int answer = prompt.askForNumber("Would you like to cook again? 1 = yes, 2 = no - ");
      
         switch (answer) {
         
            case 1: //which means yes
               if (sr.ingredients[0] >= 1 || sr.ingredients[1] >= 1 || sr.ingredients[2] >= 1) { //no apples, cherries, or carrots
                  prompt.print("You've used some ingredients. Let's see what you have now.");
                  sr.IngredientCount(prompt);
                  sr.compareRecipe(prompt);
                  WantToCook(prompt);
                  break;
               }
               else {
                  prompt.print("You don't have enough ingredients left to cook anything.");
                  break;
               }
            case 2: //which means no
               prompt.print("Ok, done cooking. Enjoy!");
               break;
            default:
               prompt.print("Invalid answer please try again!");
         
         }// End answer switch
      
      }

And finally, no more errors. You can run your program again.

If you lost track of things, here are the changes made thus far.

import java.util.Scanner;

public interface Round1
{


   public static void main (String [] args){
      final ShowRecipes first = new ShowRecipes();
      System.out.println("FIRST");
      
      final UserPrompt prompt = new UserPrompt();
      
      final Driver driver = new Driver(first);
      driver.input(prompt);
      first.compareRecipe(prompt);
      driver.WantToCook(prompt);
   
      driver.selection(prompt);
        
   }
      
   public class UserPrompt
   {
   
      final Scanner kbd = new Scanner(System.in);
   
      public int askForNumber(String prompt)
      {
      
         //Print your prompt to the command line -- I added a little extra at the end for simple reading
         System.out.print(prompt   " - ");
         
         return kbd.nextInt();
         
      }
      
      public void print(String message)
      {
      
         System.out.println(message);
      
      }
   
   }
   
   public class Driver
   {
   
      ShowRecipes sr;
      
      public Driver(ShowRecipes parameter)
      {
      
         sr = parameter;
      
      }
   
      public void selection(UserPrompt prompt){ //Can access every method in class except CookAgain
        
        //1. Cook something -- WantToCook()
        //2. See ingredient list -- IngredientCount()
        //3. See available recipes -- compareRecipe()
        //4. Modify total ingredient amounts -- input()
        //maybe  put this in ShowRecipes, create object for each to put in own classes
        
         int userChoice = 
            prompt.askForNumber(
               "What would you like to do now? Enter a number to select. \n1. Cook something \n2. See ingredients \n3. See available recipes \n4. Modify ingredient amounts"
            );
        
         switch (userChoice) {
            case 1:
               WantToCook(prompt);
               break;
            case 2: 
               sr.IngredientCount(prompt);
               break;
            case 3: 
               sr.compareRecipe(prompt);
               break;
            case 4:
               input(prompt);
               break;
            default:
               System.out.println("Invalid");
               return;
         }
         
      }
   
      public void WantToCook(UserPrompt prompt) { //Enter recipe number - deducts ingredients
        
         int recipeNum = prompt.askForNumber("What would you like to cook? Please enter the recipe number.");
         
         switch(recipeNum) {
         
            case 1: //Apple jam
               sr.setApples(sr.getApples()-3);
               prompt.print(sr.RecipeNamesA[0]);
               break;
            case 2: //Apple jelly
               prompt.print(sr.RecipeNamesA[1]);
               sr.setApples(sr.getApples()-2);
               break;
            case 3://Apple Smoothie
               prompt.print(sr.RecipeNamesA[2]);
               sr.setApples(sr.getApples()-2);
               break;
            case 4: //Apple Tart
               prompt.print(sr.RecipeNamesA[3]);
               sr.setApples(sr.getApples()-1);
               sr.setFlour(sr.getFlour()-1);
               sr.setSugar(sr.getSugar()-1);
               break;
            case 5: //Apple Pie
               prompt.print(sr.RecipeNamesA[4]);
               sr.setApples(sr.getApples()-2);
               sr.setFlour(sr.getFlour()-3);
               sr.setSugar(sr.getSugar()-2);
               break;
            case 6: //Cherry Jam
               prompt.print(sr.RecipeNamesCh[0]);
               sr.setCherries(sr.getCherries()-3);
               break;
            case 7: //Cherry Jelly
               prompt.print(sr.RecipeNamesCh[1]);
               sr.setCherries(sr.getCherries()-2);
               break;
            case 8: //Cherry Pie
               prompt.print(sr.RecipeNamesCh[2]);
               sr.setCherries(sr.getCherries()-2);
               sr.setFlour(sr.getFlour()-3);
               sr.setSugar(sr.getSugar()-2);
               break;
            case 9: //Cherry Tart
               prompt.print(sr.RecipeNamesCh[3]);
               sr.setCherries(sr.getCherries()-1);
               sr.setFlour(sr.getFlour()-1);
               sr.setSugar(sr.getSugar()-1);
               break;
            
            case 10: //Carrot juice 
               prompt.print(sr.RecipeNamesCa[0]);
               sr.setCarrots(sr.getCarrots()-2);
               break;
            case 11: //Carrot cake
               prompt.print(sr.RecipeNamesCa[1]);
               sr.setCarrots(sr.getCarrots()-1);
               sr.setFlour(sr.getFlour()-1);
               sr.setSugar(sr.getSugar()-1);
               break;
         
            case 12: //Carrot potage
               prompt.print((sr.RecipeNamesCa[2]));
               sr.setCarrots(sr.getCarrots()-2);
               sr.setFlour(sr.getFlour()-1); 
               break;
            
            default:
               prompt.print("Invalid entry.");
         }//End recipeNum switch
        
         prompt.print("You have cooked a thing! Good job!");
         CookAgain(prompt); //cook again? prompt
      } //end WantToCook method
   
      public void input(UserPrompt prompt) { // user inputs ingredient counts
      
        //Asks user for ingredients on-hand
         prompt.print("Please enter how many of each ingredient you have:");
      
         sr.setApples(prompt.askForNumber(sr.ingredientsNames[0]));
      
         sr.setCherries(prompt.askForNumber(sr.ingredientsNames[1]));
      
         sr.setCarrots(prompt.askForNumber(sr.ingredientsNames[2]));
      
         sr.setFlour(prompt.askForNumber(sr.ingredientsNames[3]));
      
         sr.setSugar(prompt.askForNumber(sr.ingredientsNames[4]));
      }//end user ingredient input
   
      public void CookAgain(UserPrompt prompt) {   
      
         int answer = prompt.askForNumber("Would you like to cook again? 1 = yes, 2 = no - ");
      
         switch (answer) {
         
            case 1: //which means yes
               if (sr.ingredients[0] >= 1 || sr.ingredients[1] >= 1 || sr.ingredients[2] >= 1) { //no apples, cherries, or carrots
                  prompt.print("You've used some ingredients. Let's see what you have now.");
                  sr.IngredientCount(prompt);
                  sr.compareRecipe(prompt);
                  WantToCook(prompt);
                  break;
               }
               else {
                  prompt.print("You don't have enough ingredients left to cook anything.");
                  break;
               }
            case 2: //which means no
               prompt.print("Ok, done cooking. Enjoy!");
               break;
            default:
               prompt.print("Invalid answer please try again!");
         
         }// End answer switch
      
      }
      
   }

//object class
   public class ShowRecipes {
   
    
      Scanner kbd = new Scanner(System.in);
   
    //ingredient names array
      public String ingredientsNames[] = {"Apples", "Cherry", "Carrot", "Flour ", "Sugar "};
   
    //ingredients array - setters will set
      public int[] ingredients = new int[5];
   
    //Basic constructor
      public ShowRecipes() {
         ingredients[0] = 1; //apple
         ingredients[1] = 1; //cherry
         ingredients[2] = 1;//carrot
         ingredients[3] = 0;//flour
         ingredients[4] = 0;//sugar
      }
   
    //overloaded constructor that covers each field
      public ShowRecipes(int apples, int cherries, int carrots, int flour, int sugar) {
         System.out.println("overloaded");
         setApples(apples);
         setCherries(cherries);
         setCarrots(carrots);
         setFlour(flour);
         setSugar(sugar);
      }
   
      public void IngredientCount(UserPrompt prompt) { //Lists ingredient input counts
         prompt.print("According to your input, you have:");
      
         prompt.print("Ingredient\tValue");
         for(int counter=0;counter<ingredients.length;counter  ) {
            prompt.print(ingredientsNames[counter]   "\t\t"   ingredients[counter]);
         }//end for loop
      }//end ingredient listing
    
    
    //Setters - set from input()
    //Apple setter
      public void setApples(int apples){
         ingredients[0] = apples;
      }
    //Cherry setter
      public void setCherries(int cherries){
         ingredients[1] = cherries;
      }
    //Carrot setter
      public void setCarrots(int carrots){
         ingredients[2]= carrots;
      }
    //Flour setter
      public void setFlour(int flour){
         ingredients[3] = flour;
      }
    //Sugar setter
      public void setSugar(int sugar){
         ingredients[4] = sugar;
      }
    
    //Getters
    //Apple getter
      public int getApples(){
         return ingredients[0];
      }
    //Cherry getter
      public int getCherries(){
         return ingredients[1];
      }
    //Carrot getter
      public int getCarrots(){
         return ingredients[2];
      }
    //Flour getter
      public int getFlour(){
         return ingredients[3];
      }
    //Sugar getter
      public int getSugar(){
         return ingredients[4];
      }
   
      public void compareRecipe(UserPrompt prompt) {
      
         prompt.print("According to the input, you can make:");
      
        //Apples
         if (ingredients[0] >= 3) {
            prompt.print(RecipeNamesA[0]); //Apple Jam
         }
         if (ingredients[0] >= 2) {
            prompt.print(RecipeNamesA[1]); //Apple Jelly
            prompt.print(RecipeNamesA[2]); //Apple Smoothie
         }
      
         if (ingredients[0] >= 1 && ingredients[3] >= 1){
            prompt.print(RecipeNamesA[3]); //Apple Tart
         }
      
         if (ingredients[0] >= 2 && ingredients[4] >= 2 && ingredients[3] >= 1){
            prompt.print(RecipeNamesA[4]); //Apple Pie
         }
         if (ingredients[0] == 0) {
            prompt.print("No apple recipes");
         }
      
        //Cherries
         if (ingredients[1] >= 3) {
            prompt.print(RecipeNamesCh[0]); //Cherry Jam
         }
         if (ingredients[1] >= 2) {
            prompt.print(RecipeNamesCh[1]); //Cherry Jelly
         }
      
         if (ingredients[1] >= 2 && ingredients[3] >= 3 && ingredients[4] >= 2) {
            prompt.print(RecipeNamesCh[2]); //Cherry Pie
         }
         if (ingredients[1] >= 1 && ingredients[3] <= 1 && ingredients[4] >= 1) {
            prompt.print(RecipeNamesCh[3]); //Cherry Tart
         }
         if (ingredients[1] == 0) {
            prompt.print("No cherry recipes");
         }
      
        //Carrots
         if (ingredients[2] >= 2) {
            prompt.print(RecipeNamesCa[0]); //Carrot juice 
         }
         if (ingredients[2] >= 2 && ingredients[3] >= 1) {
            prompt.print(RecipeNamesCa[2]); //Carrot potage
         }
      
         if (ingredients[2] >= 1 && ingredients[3] >= 1 && ingredients[4] >= 1){
            prompt.print(RecipeNamesCa[1]); //Carrot cake
         }   
         if (ingredients[2] == 0) {
            prompt.print("No carrot recipes");
         }
        
      }
   
      private String RecipeNamesA[] = {"1. Apple Jam", "2. Apple Jelly", "3. Apple Smoothie", "4. Apple Tart", "5. Apple Pie"}; //Apple recipes
      private String RecipeNamesCh[] = {"6. Cherry Jam", "7. Cherry Jelly", "8. Cherry Pie", "9. Cherry Tart"}; //Cherry recipes
      private String RecipeNamesCa[] = {"10. Carrot Juice", "11. Carrot Cake","12. Carrot Potage"}; //Carrot recipes
   
   }

}

You can copy and paste the above and run it, and you should see that the program runs.

Now that that is done, let's finalize cleaning up the tight coupling (as best as we can), then we can move onto the remaining 3 points (global state, complex methods, and enums).

So far, we have taken care of the following methods.

  • ShowRecipes()
  • input()
  • IngredientCount()
  • selection()

Now, I know we addressed other methods, but we only moved them around, and made the modifications necessary to work. We didn't actually focus on making them loosely coupled, so let's continue with our list.

Now, the next bunch of methods are very similar, so I will handle them as a whole. These next batch of methods are your setters and getters. Now, these methods do literally nothing more than manipulate state, so the methods themselves aren't wrong. That said, seeing these methods lends us to one interesting observation - in some points of your code, you use getters and setters to read/modify state, but in other parts of your code, you just modify the instance fields directly.

The act of modifying instance fields directly is a great example of tight-coupling. What happens if you decide to one day use a List<T> instead of an int[] to hold your ingredients? Well, any method that uses setters and getters will be fine, since you can just update the setters and getters alone. But, the code that directly manipulates state will be in trouble, because now they all have to change to handle a List<T> instead of an int[].

Now, to keep ourselves honest, I am actually going to put all of these classes into separate files from now on. As you might have noticed, I have wrapped all of this into a giant interface called Round1. This helps keep things together in one bundle, but it also allows us to cheat and just edit anything contained within the interface, even private variables.

So, I will put the Driver class into a Driver.java file, ShowRecipes class into ShowRecipes.java, and then the UserPrompt class into UserPrompt.java. And to keep things even simpler, I will add the main method inside of the Driver class. Also, to prevent errors or file naming problems, I will rename ShowRecipes to be ShowRecipesNew. I have updated all of the code to reflect this change.

Your code base should now look like this.

Driver.java

public class Driver
{

   ShowRecipesNew sr;
   
   public Driver(ShowRecipesNew parameter)
   {
   
      sr = parameter;
   
   }

   public static void main (String [] args){
      final ShowRecipesNew first = new ShowRecipesNew();
      System.out.println("FIRST");
      
      final UserPrompt prompt = new UserPrompt();
      
      final Driver driver = new Driver(first);
      driver.input(prompt);
      first.compareRecipe(prompt);
      driver.WantToCook(prompt);
   
      driver.selection(prompt);
        
   }
      
   public void selection(UserPrompt prompt){ //Can access every method in class except CookAgain
     
     //1. Cook something -- WantToCook()
     //2. See ingredient list -- IngredientCount()
     //3. See available recipes -- compareRecipe()
     //4. Modify total ingredient amounts -- input()
     //maybe  put this in ShowRecipes, create object for each to put in own classes
     
      int userChoice = 
         prompt.askForNumber(
            "What would you like to do now? Enter a number to select. \n1. Cook something \n2. See ingredients \n3. See available recipes \n4. Modify ingredient amounts"
         );
     
      switch (userChoice) {
         case 1:
            WantToCook(prompt);
            break;
         case 2: 
            sr.IngredientCount(prompt);
            break;
         case 3: 
            sr.compareRecipe(prompt);
            break;
         case 4:
            input(prompt);
            break;
         default:
            System.out.println("Invalid");
            return;
      }
      
   }

   public void WantToCook(UserPrompt prompt) { //Enter recipe number - deducts ingredients
     
      int recipeNum = prompt.askForNumber("What would you like to cook? Please enter the recipe number.");
      
      switch(recipeNum) {
      
         case 1: //Apple jam
            sr.setApples(sr.getApples()-3);
            prompt.print(sr.RecipeNamesA[0]);
            break;
         case 2: //Apple jelly
            prompt.print(sr.RecipeNamesA[1]);
            sr.setApples(sr.getApples()-2);
            break;
         case 3://Apple Smoothie
            prompt.print(sr.RecipeNamesA[2]);
            sr.setApples(sr.getApples()-2);
            break;
         case 4: //Apple Tart
            prompt.print(sr.RecipeNamesA[3]);
            sr.setApples(sr.getApples()-1);
            sr.setFlour(sr.getFlour()-1);
            sr.setSugar(sr.getSugar()-1);
            break;
         case 5: //Apple Pie
            prompt.print(sr.RecipeNamesA[4]);
            sr.setApples(sr.getApples()-2);
            sr.setFlour(sr.getFlour()-3);
            sr.setSugar(sr.getSugar()-2);
            break;
         case 6: //Cherry Jam
            prompt.print(sr.RecipeNamesCh[0]);
            sr.setCherries(sr.getCherries()-3);
            break;
         case 7: //Cherry Jelly
            prompt.print(sr.RecipeNamesCh[1]);
            sr.setCherries(sr.getCherries()-2);
            break;
         case 8: //Cherry Pie
            prompt.print(sr.RecipeNamesCh[2]);
            sr.setCherries(sr.getCherries()-2);
            sr.setFlour(sr.getFlour()-3);
            sr.setSugar(sr.getSugar()-2);
            break;
         case 9: //Cherry Tart
            prompt.print(sr.RecipeNamesCh[3]);
            sr.setCherries(sr.getCherries()-1);
            sr.setFlour(sr.getFlour()-1);
            sr.setSugar(sr.getSugar()-1);
            break;
         
         case 10: //Carrot juice 
            prompt.print(sr.RecipeNamesCa[0]);
            sr.setCarrots(sr.getCarrots()-2);
            break;
         case 11: //Carrot cake
            prompt.print(sr.RecipeNamesCa[1]);
            sr.setCarrots(sr.getCarrots()-1);
            sr.setFlour(sr.getFlour()-1);
            sr.setSugar(sr.getSugar()-1);
            break;
      
         case 12: //Carrot potage
            prompt.print((sr.RecipeNamesCa[2]));
            sr.setCarrots(sr.getCarrots()-2);
            sr.setFlour(sr.getFlour()-1); 
            break;
         
         default:
            prompt.print("Invalid entry.");
      }//End recipeNum switch
     
      prompt.print("You have cooked a thing! Good job!");
      CookAgain(prompt); //cook again? prompt
   } //end WantToCook method

   public void input(UserPrompt prompt) { // user inputs ingredient counts
   
     //Asks user for ingredients on-hand
      prompt.print("Please enter how many of each ingredient you have:");
   
      sr.setApples(prompt.askForNumber(sr.ingredientsNames[0]));
   
      sr.setCherries(prompt.askForNumber(sr.ingredientsNames[1]));
   
      sr.setCarrots(prompt.askForNumber(sr.ingredientsNames[2]));
   
      sr.setFlour(prompt.askForNumber(sr.ingredientsNames[3]));
   
      sr.setSugar(prompt.askForNumber(sr.ingredientsNames[4]));
   }//end user ingredient input

   public void CookAgain(UserPrompt prompt) {   
   
      int answer = prompt.askForNumber("Would you like to cook again? 1 = yes, 2 = no - ");
   
      switch (answer) {
      
         case 1: //which means yes
            if (sr.ingredients[0] >= 1 || sr.ingredients[1] >= 1 || sr.ingredients[2] >= 1) { //no apples, cherries, or carrots
               prompt.print("You've used some ingredients. Let's see what you have now.");
               sr.IngredientCount(prompt);
               sr.compareRecipe(prompt);
               WantToCook(prompt);
               break;
            }
            else {
               prompt.print("You don't have enough ingredients left to cook anything.");
               break;
            }
         case 2: //which means no
            prompt.print("Ok, done cooking. Enjoy!");
            break;
         default:
            prompt.print("Invalid answer please try again!");
      
      }// End answer switch
   
   }
   
}

UserPrompt.java

import java.util.Scanner;

public class UserPrompt
{

   final Scanner kbd = new Scanner(System.in);

   public int askForNumber(String prompt)
   {
   
      //Print your prompt to the command line -- I added a little extra at the end for simple reading
      System.out.print(prompt   " - ");
      
      return kbd.nextInt();
      
   }
   
   public void print(String message)
   {
   
      System.out.println(message);
   
   }

}

CONTINUING IN PART 4

CodePudding user response:

PART 4

ShowRecipesNew.java

import java.util.Scanner;

//object class
public class ShowRecipesNew {

   Scanner kbd = new Scanner(System.in);

 //ingredient names array
   public String ingredientsNames[] = {"Apples", "Cherry", "Carrot", "Flour ", "Sugar "};

 //ingredients array - setters will set
   public int[] ingredients = new int[5];

 //Basic constructor
   public ShowRecipesNew() {
      ingredients[0] = 1; //apple
      ingredients[1] = 1; //cherry
      ingredients[2] = 1;//carrot
      ingredients[3] = 0;//flour
      ingredients[4] = 0;//sugar
   }

 //overloaded constructor that covers each field
   public ShowRecipesNew(int apples, int cherries, int carrots, int flour, int sugar) {
      System.out.println("overloaded");
      setApples(apples);
      setCherries(cherries);
      setCarrots(carrots);
      setFlour(flour);
      setSugar(sugar);
   }

   public void IngredientCount(UserPrompt prompt) { //Lists ingredient input counts
      prompt.print("According to your input, you have:");
   
      prompt.print("Ingredient\tValue");
      for(int counter=0;counter<ingredients.length;counter  ) {
         prompt.print(ingredientsNames[counter]   "\t\t"   ingredients[counter]);
      }//end for loop
   }//end ingredient listing
 
 
 //Setters - set from input()
 //Apple setter
   public void setApples(int apples){
      ingredients[0] = apples;
   }
 //Cherry setter
   public void setCherries(int cherries){
      ingredients[1] = cherries;
   }
 //Carrot setter
   public void setCarrots(int carrots){
      ingredients[2]= carrots;
   }
 //Flour setter
   public void setFlour(int flour){
      ingredients[3] = flour;
   }
 //Sugar setter
   public void setSugar(int sugar){
      ingredients[4] = sugar;
   }
 
 //Getters
 //Apple getter
   public int getApples(){
      return ingredients[0];
   }
 //Cherry getter
   public int getCherries(){
      return ingredients[1];
   }
 //Carrot getter
   public int getCarrots(){
      return ingredients[2];
   }
 //Flour getter
   public int getFlour(){
      return ingredients[3];
   }
 //Sugar getter
   public int getSugar(){
      return ingredients[4];
   }

   public void compareRecipe(UserPrompt prompt) {
   
      prompt.print("According to the input, you can make:");
   
     //Apples
      if (ingredients[0] >= 3) {
         prompt.print(RecipeNamesA[0]); //Apple Jam
      }
      if (ingredients[0] >= 2) {
         prompt.print(RecipeNamesA[1]); //Apple Jelly
         prompt.print(RecipeNamesA[2]); //Apple Smoothie
      }
   
      if (ingredients[0] >= 1 && ingredients[3] >= 1){
         prompt.print(RecipeNamesA[3]); //Apple Tart
      }
   
      if (ingredients[0] >= 2 && ingredients[4] >= 2 && ingredients[3] >= 1){
         prompt.print(RecipeNamesA[4]); //Apple Pie
      }
      if (ingredients[0] == 0) {
         prompt.print("No apple recipes");
      }
   
     //Cherries
      if (ingredients[1] >= 3) {
         prompt.print(RecipeNamesCh[0]); //Cherry Jam
      }
      if (ingredients[1] >= 2) {
         prompt.print(RecipeNamesCh[1]); //Cherry Jelly
      }
   
      if (ingredients[1] >= 2 && ingredients[3] >= 3 && ingredients[4] >= 2) {
         prompt.print(RecipeNamesCh[2]); //Cherry Pie
      }
      if (ingredients[1] >= 1 && ingredients[3] <= 1 && ingredients[4] >= 1) {
         prompt.print(RecipeNamesCh[3]); //Cherry Tart
      }
      if (ingredients[1] == 0) {
         prompt.print("No cherry recipes");
      }
   
     //Carrots
      if (ingredients[2] >= 2) {
         prompt.print(RecipeNamesCa[0]); //Carrot juice 
      }
      if (ingredients[2] >= 2 && ingredients[3] >= 1) {
         prompt.print(RecipeNamesCa[2]); //Carrot potage
      }
   
      if (ingredients[2] >= 1 && ingredients[3] >= 1 && ingredients[4] >= 1){
         prompt.print(RecipeNamesCa[1]); //Carrot cake
      }   
      if (ingredients[2] == 0) {
         prompt.print("No carrot recipes");
      }
     
   }

   private String RecipeNamesA[] = {"1. Apple Jam", "2. Apple Jelly", "3. Apple Smoothie", "4. Apple Tart", "5. Apple Pie"}; //Apple recipes
   private String RecipeNamesCh[] = {"6. Cherry Jam", "7. Cherry Jelly", "8. Cherry Pie", "9. Cherry Tart"}; //Cherry recipes
   private String RecipeNamesCa[] = {"10. Carrot Juice", "11. Carrot Cake","12. Carrot Potage"}; //Carrot recipes

}

Now, if you try to compile these 3 files, we'll see that UserPrompt and ShowRecipesNew have no errors, but Driver has a whole bunch. Here is one.

Driver.java:69: error: RecipeNamesA has private access in ShowRecipesNew
            prompt.print(sr.RecipeNamesA[0]);
                           ^

So now, the compiler has pointed out all of the situations where we have made direct interaction with private instance fields. As mentioned before, this another form of tight coupling, and in fact, it starts to slip over into the second point I was talking about - global state. We are going to fixing most of the global state issues here, so there may not be much left to correct once we finish up.

Anyways, the way to resolve those errors is by using setters and/or getters. You had the right idea going with the setters and getters you added already, but let's add some more for the rest of the private instance fields in ShowRecipe. In this case, we only need to use getters, since the private fields are only being read, not set.

public class ShowRecipesNew
{

   //ignore the lines that come before this one

   private String RecipeNamesA[] = {"1. Apple Jam", "2. Apple Jelly", "3. Apple Smoothie", "4. Apple Tart", "5. Apple Pie"}; //Apple recipes
   private String RecipeNamesCh[] = {"6. Cherry Jam", "7. Cherry Jelly", "8. Cherry Pie", "9. Cherry Tart"}; //Cherry recipes
   private String RecipeNamesCa[] = {"10. Carrot Juice", "11. Carrot Cake","12. Carrot Potage"}; //Carrot recipes

   public String[] getAppleRecipeNames()
   {
   
      return RecipeNamesA;
   
   }

   public String[] getCherryRecipeNames()
   {
   
      return RecipeNamesCh;
   
   }

   public String[] getCarrotRecipeNames()
   {
   
      return RecipeNamesCa;
   
   }

}

Ok, now that we have some getters, let's replace the failing lines in Driver with those new getters. Seems like they were all in the WantToCook() method.

   public void WantToCook(UserPrompt prompt) { //Enter recipe number - deducts ingredients
     
      int recipeNum = prompt.askForNumber("What would you like to cook? Please enter the recipe number.");
      
      switch(recipeNum) {
      
         case 1: //Apple jam
            sr.setApples(sr.getApples()-3);
            prompt.print(sr.getAppleRecipeNames()[0]);
            break;
         case 2: //Apple jelly
            prompt.print(sr.getAppleRecipeNames()[1]);
            sr.setApples(sr.getApples()-2);
            break;
         case 3://Apple Smoothie
            prompt.print(sr.getAppleRecipeNames()[2]);
            sr.setApples(sr.getApples()-2);
            break;
         case 4: //Apple Tart
            prompt.print(sr.getAppleRecipeNames()[3]);
            sr.setApples(sr.getApples()-1);
            sr.setFlour(sr.getFlour()-1);
            sr.setSugar(sr.getSugar()-1);
            break;
         case 5: //Apple Pie
            prompt.print(sr.getAppleRecipeNames()[4]);
            sr.setApples(sr.getApples()-2);
            sr.setFlour(sr.getFlour()-3);
            sr.setSugar(sr.getSugar()-2);
            break;
         case 6: //Cherry Jam
            prompt.print(sr.getCherryRecipeNames()[0]);
            sr.setCherries(sr.getCherries()-3);
            break;
         case 7: //Cherry Jelly
            prompt.print(sr.getCherryRecipeNames()[1]);
            sr.setCherries(sr.getCherries()-2);
            break;
         case 8: //Cherry Pie
            prompt.print(sr.getCherryRecipeNames()[2]);
            sr.setCherries(sr.getCherries()-2);
            sr.setFlour(sr.getFlour()-3);
            sr.setSugar(sr.getSugar()-2);
            break;
         case 9: //Cherry Tart
            prompt.print(sr.getCherryRecipeNames()[3]);
            sr.setCherries(sr.getCherries()-1);
            sr.setFlour(sr.getFlour()-1);
            sr.setSugar(sr.getSugar()-1);
            break;
         
         case 10: //Carrot juice 
            prompt.print(sr.getCarrotRecipeNames()[0]);
            sr.setCarrots(sr.getCarrots()-2);
            break;
         case 11: //Carrot cake
            prompt.print(sr.getCarrotRecipeNames()[1]);
            sr.setCarrots(sr.getCarrots()-1);
            sr.setFlour(sr.getFlour()-1);
            sr.setSugar(sr.getSugar()-1);
            break;
      
         case 12: //Carrot potage
            prompt.print((sr.getCarrotRecipeNames()[2]));
            sr.setCarrots(sr.getCarrots()-2);
            sr.setFlour(sr.getFlour()-1); 
            break;
         
         default:
            prompt.print("Invalid entry.");
      }//End recipeNum switch
     
      prompt.print("You have cooked a thing! Good job!");
      CookAgain(prompt); //cook again? prompt
   } //end WantToCook method

Now, if we compile, we get no more errors. And more importantly, the code runs too.

At this point, you could technically stop and say that the original goal has completed. If so, feel free to stop reading now. However, I will continue performing all the tasks I agreed to.

Moving along, the list of methods that we have made loosely coupled is as follows.

  • ShowRecipes()
  • input()
  • IngredientCount()
  • selection()
  • The getters and setters

On to the next method, we have compareRecipe() in ShowRecipes.

Now, this one is a little bit unclear on how best to move forward. In one perspective, you could say that this method just prints out the state of ShowRecipes. But on the other hand, you could say that it decides what to print based on the state of ShowRecipes.

Me personally, the thing that pushes it just over the edge for me is the fact that it uses what we like to call Magic Numbers. What if we add a new recipe or a new ingredient? Sure, they likely wouldn't cause any compilation errors, but it definitely would cause logic errors, as mentioned previously. That said, the way that we would resolve this is by using enums actually. So, I will save this change until we reach the enum portion, just to keep things easy to follow. But yes, this method still has tight coupling to the indexes of the various int arrays. Trying to solve it without enums would just be a waste of effort since the best solution is definitely, so I will defer this change until then.

Next on the list is WantToCook() in Driver. We already moved this over, so the method is in the right area. And thanks to the changes we made before (plugging in UserPrompt and ShowRecipes), most of the tight coupling has been removed. However, just like the previous method, we are tightly coupled to the indexes. And just like before, I will solve this tight coupling to the indexes in the enums section.

But otherwise, that's all the methods for checking tight coupling. Now, let's move on to the next section.

#2 Global State and Public Instance Fields

When an object lets its internal state be public, it allows other programs/classes/etc to depend on the internal implementation of the class. As mentioned before - if you are currently using an int[] and then decide to switch over to a List<Integer>, then anything that was depending on the internal state will immediately break, and you will need to do some debugging and refactoring to correct the compilation errors. However, if you keep your internal state private and have clearly defined getters and setters, then you can make all the changes internally, and then just modify your getters and setters to accomodate the new internal state. For example, let's say you have getters that return int[] and setters that accept int[]. Well, when your internal state decides to use a list instead, your getter should still return int[] and your setter should still accept int[], but the getter should convert the List<Integer> into an int[] array, and the setter should convert the accepted int[] parameter into a List<Integer> before modifying internal state.

As you might have guessed, that means that we need to turn all of the instance fields into private instance fields. Each class has at least one instance field that needs to change. Let's cycle through the classes and get them all.

  • Driver - looks like our instance field ShowRecipesNew sr was only set to package-protected as opposed to private. All that is required is to put a private in front of it and we are done. Like this -- private ShowRecipesNew sr;

  • UserPrompt - looks like we did the same mistake for the Scanner in UserPrompt. This is just as simple and everything works just fine after. private final Scanner kbd = new Scanner(System.in);

  • ShowRecipesNew - Unfortunately, this is where we will need to make some heavier modifications. For starters, there are 3 instance fields that are not private.

   Scanner kbd = new Scanner(System.in);

 //ingredient names array
   public String ingredientsNames[] = {"Apples", "Cherry", "Carrot", "Flour ", "Sugar "};

 //ingredients array - setters will set
   public int[] ingredients = new int[5];

Thankfully, we can eliminate one of them already. The Scanner shouldn't be there. All of our scanning is being handled by UserPrompt, so we can just plain delete that variable completely.

Unfortunately, the other 2 are much harder. They are actually being used up and down the Driver class, so we will need to do some heavy rewriting. In short, anywhere that we directly accessed these instance fields, we will now do through a getter. Much like previously, this is a tedious task so I will just knock it out all at once.

Here are the getters in ShowRecipesNew.


   public String[] getIngredientNames()
   {
   
      return ingredientsNames;
   
   }

   public int[] getIngredientsBag()
   {
   
      return ingredients;
   
   }

And here is the refactored Driver.

public class Driver
{

   private ShowRecipesNew sr;
   
   public Driver(ShowRecipesNew parameter)
   {
   
      sr = parameter;
   
   }

   public static void main (String [] args){
      final ShowRecipesNew first = new ShowRecipesNew();
      System.out.println("FIRST");
      
      final UserPrompt prompt = new UserPrompt();
      
      final Driver driver = new Driver(first);
      driver.input(prompt);
      first.compareRecipe(prompt);
      driver.WantToCook(prompt);
   
      driver.selection(prompt);
        
   }
      
   public void selection(UserPrompt prompt){ //Can access every method in class except CookAgain
     
     //1. Cook something -- WantToCook()
     //2. See ingredient list -- IngredientCount()
     //3. See available recipes -- compareRecipe()
     //4. Modify total ingredient amounts -- input()
     //maybe  put this in ShowRecipes, create object for each to put in own classes
     
      int userChoice = 
         prompt.askForNumber(
            "What would you like to do now? Enter a number to select. \n1. Cook something \n2. See ingredients \n3. See available recipes \n4. Modify ingredient amounts"
         );
     
      switch (userChoice) {
         case 1:
            WantToCook(prompt);
            break;
         case 2: 
            sr.IngredientCount(prompt);
            break;
         case 3: 
            sr.compareRecipe(prompt);
            break;
         case 4:
            input(prompt);
            break;
         default:
            System.out.println("Invalid");
            return;
      }
      
   }

   public void WantToCook(UserPrompt prompt) { //Enter recipe number - deducts ingredients
     
      int recipeNum = prompt.askForNumber("What would you like to cook? Please enter the recipe number.");
      
      switch(recipeNum) {
      
         case 1: //Apple jam
            sr.setApples(sr.getApples()-3);
            prompt.print(sr.getAppleRecipeNames()[0]);
            break;
         case 2: //Apple jelly
            prompt.print(sr.getAppleRecipeNames()[1]);
            sr.setApples(sr.getApples()-2);
            break;
         case 3://Apple Smoothie
            prompt.print(sr.getAppleRecipeNames()[2]);
            sr.setApples(sr.getApples()-2);
            break;
         case 4: //Apple Tart
            prompt.print(sr.getAppleRecipeNames()[3]);
            sr.setApples(sr.getApples()-1);
            sr.setFlour(sr.getFlour()-1);
            sr.setSugar(sr.getSugar()-1);
            break;
         case 5: //Apple Pie
            prompt.print(sr.getAppleRecipeNames()[4]);
            sr.setApples(sr.getApples()-2);
            sr.setFlour(sr.getFlour()-3);
            sr.setSugar(sr.getSugar()-2);
            break;
         case 6: //Cherry Jam
            prompt.print(sr.getCherryRecipeNames()[0]);
            sr.setCherries(sr.getCherries()-3);
            break;
         case 7: //Cherry Jelly
            prompt.print(sr.getCherryRecipeNames()[1]);
            sr.setCherries(sr.getCherries()-2);
            break;
         case 8: //Cherry Pie
            prompt.print(sr.getCherryRecipeNames()[2]);
            sr.setCherries(sr.getCherries()-2);
            sr.setFlour(sr.getFlour()-3);
            sr.setSugar(sr.getSugar()-2);
            break;
         case 9: //Cherry Tart
            prompt.print(sr.getCherryRecipeNames()[3]);
            sr.setCherries(sr.getCherries()-1);
            sr.setFlour(sr.getFlour()-1);
            sr.setSugar(sr.getSugar()-1);
            break;
         
         case 10: //Carrot juice 
            prompt.print(sr.getCarrotRecipeNames()[0]);
            sr.setCarrots(sr.getCarrots()-2);
            break;
         case 11: //Carrot cake
            prompt.print(sr.getCarrotRecipeNames()[1]);
            sr.setCarrots(sr.getCarrots()-1);
            sr.setFlour(sr.getFlour()-1);
            sr.setSugar(sr.getSugar()-1);
            break;
      
         case 12: //Carrot potage
            prompt.print((sr.getCarrotRecipeNames()[2]));
            sr.setCarrots(sr.getCarrots()-2);
            sr.setFlour(sr.getFlour()-1); 
            break;
         
         default:
            prompt.print("Invalid entry.");
      }//End recipeNum switch
     
      prompt.print("You have cooked a thing! Good job!");
      CookAgain(prompt); //cook again? prompt
   } //end WantToCook method

   public void input(UserPrompt prompt) { // user inputs ingredient counts
   
     //Asks user for ingredients on-hand
      prompt.print("Please enter how many of each ingredient you have:");
   
      sr.setApples(prompt.askForNumber(sr.getIngredientNames()[0]));
   
      sr.setCherries(prompt.askForNumber(sr.getIngredientNames()[1]));
   
      sr.setCarrots(prompt.askForNumber(sr.getIngredientNames()[2]));
   
      sr.setFlour(prompt.askForNumber(sr.getIngredientNames()[3]));
   
      sr.setSugar(prompt.askForNumber(sr.getIngredientNames()[4]));
   }//end user ingredient input

   public void CookAgain(UserPrompt prompt) {   
   
      int answer = prompt.askForNumber("Would you like to cook again? 1 = yes, 2 = no - ");
   
      switch (answer) {
      
         case 1: //which means yes
            if (sr.getIngredientsBag()[0] >= 1 || sr.getIngredientsBag()[1] >= 1 || sr.getIngredientsBag()[2] >= 1) { //no apples, cherries, or carrots
               prompt.print("You've used some ingredients. Let's see what you have now.");
               sr.IngredientCount(prompt);
               sr.compareRecipe(prompt);
               WantToCook(prompt);
               break;
            }
            else {
               prompt.print("You don't have enough ingredients left to cook anything.");
               break;
            }
         case 2: //which means no
            prompt.print("Ok, done cooking. Enjoy!");
            break;
         default:
            prompt.print("Invalid answer please try again!");
      
      }// End answer switch
   
   }
   
}

And now, we have fully eliminated all global access and direct access to instance fields. Now, we just need to maintain our getters/setters in order to make sure that all of our objects work well together.

On to the next point.

#3 Complex and large methods

In your defense, Java is a language that makes it very easy to write long methods. Most people even say the languages lends itself to verbosity. Regardless, methods that are too complex end up becoming too difficult to maintain. This especially becomes a problem when you want to add more functionality to your program. I brought this up before, but what if you want to add a new ingredient to your list? Well, all those complex methods that span 50 lines will now need to be updated to handle this new ingredient as well.

Funnily enough, literally every single change I would make to shorten these methods would involve an enum, so I am going to combine the next part into this one.

#4 Java Enums

This is by far the biggest improvement we can make to this project. I said it before, and I'll say it again - this type of problem practically demands that you use Java Enums. And since Java enums are so powerful and are the best out there, getting practice using them now will be good for you.

That said, enums can be confusing. If this section doesn't make much sense and/or is hard to understand, feel free to bypass this one to focus on the previous sections.

But going back to the problem, the String[] ingredientsNames would be better represented as a separate enum class called Ingredient. This new Ingredient class would go into a file called Ingredient.java.

public enum Ingredient
{

   APPLE,
   CHERRY,
   CARROT,
   FLOUR,
   SUGAR,
   ;

}

Great, now that we have the Ingredient enum, let's also make one for the recipes. We can call this enum Recipe and put it into Recipe.java.

public enum Recipe
{

   APPLE_JAM,
   APPLE_JELLY,
   APPLE_SMOOTHIE,
   APPLE_TART,
   APPLE_PIE,
   CHERRY_JAM,
   CHERRY_JELLY,
   CHERRY_PIE,
   CHERRY_TART,
   CARROT_JUICE,
   CARROT_CAKE,
   CARROT_POTAGE,
   ;

}

Ok, now that that one is ready too, we can start using these enums in place of arbitrary String values.

The first thing that should definitely change is the int[] ingredients instance field in ShowRecipesNew class. That should be replaced with an [EnumMap<E>][4]. Be sure to import it using the following imports.

import java.util.EnumMap;
import java.util.Map;

And here is the change.

public class ShowRecipesNew {
   //ignore the lines before this one
   private EnumMap<Ingredient, Integer> ingredients = new EnumMap<>(Ingredient.class);
   //ignore the lines after this one
}

This map will tell us how many of each ingredient there is. What makes this so useful though is that, if we ever decide to add a new ingredient like a banana, all we need to do is add BANANA to Ingredient.

Anyways, once we compile this, several things break.

First, the no argument constructor that gives default values for all of the ingredients is expecting an array. We can fix this by modifying the Ingredient enum to hold the default values instead.

public enum Ingredient
{

   APPLE(1),
   CHERRY(1),
   CARROT(1),
   FLOUR(0),
   SUGAR(0),
   ;
   
   private final int defaultValue;
   
   Ingredient(int input)
   {
   
      defaultValue = input;
   
   }

   public int defaultValue()
   {
   
      return defaultValue;
   
   }

}

This is a useful change, because now, if we ever decide to add a new ingredient to Ingredient, we must also give that new ingredient a default value, otherwise there will be compilation errors. The reason why this is so powerful is because it prevents you from running into logic errors by forgetting to set something. And we're about to see a lot more.

Now, let's modify the constructor to use those default values. We will be using the values() method from Ingredient. This method is one that is automatically included for you, though you can't see it. Methods like this that are automatically included without being seen are called intrinsic methods.

public class ShowRecipesNew {

   //ignore the lines before this one
   public ShowRecipesNew() {
      for (Ingredient each : Ingredient.values())
      {
         ingredients.put(each, each.defaultValue());
      }
   }
   //ignore the lines after this one
}

Again, this is definitely more complex, so feel free to skip over this if it gets too complicated. But in short, the values() method in Ingredient returns all of the enum values, so APPLE, CHERRY, CARROT, etc. Then, we loop through all of those enum values using a for-each loop. Once inside the loop, we start putting values on the map. Previously, using your old version with the int[], we would use the index to know which ingredient we were specifying the number of. But for an EnumMap, we don't care about index. We use a key-value pair relationship to find out how much of each Ingredient there is. As for defining how much, we pull the defaultValue() from the enum earlier on. Each enum value has one specified in the Ingredient enum, so it is easy to simply pull and use it.

Once we compile this, the constructor is now fixed. But there are more errors. Now, the getter for ingredients is broken. This is pretty easy to fix, we just change the return type. It's important to note though - I am only changing the return type because I believe it will make the program much easier to use and develop for. In general, it's better to avoid changing the return types of public methods, especially ones that other classes depend upon. Otherwise, it will cause errors.

Here is the change.

   public EnumMap<Ingredient, Integer> getIngredientsBag()
   {
   
      return ingredients;
   
   }

After compiling this, the next error we get is for the for loop in the method IngredientCount(). This is easily dealt with by using a for each loop. Specifically, we are going to be looping over the entrySet() method from the EnumMap. The entrySet() method allows us to loop through both the key and it's adjacent value. In the case of ingredients, that means that we can fetch the Ingredient, and the quantity of it, which is exactly what we want.

   public ShowRecipesNew() {
      for (Ingredient each : Ingredient.values())
      {
         ingredients.put(each, each.defaultValue());
      }
   }

Now that takes care of the errors in the constructor. Next, we get errors in the batch of getters and setters for Apple, Cherry, etc. However, this is yet another powerful feature of enums - we don't need those setters or getters anymore. We can combine all of those getters into a single getter and all those setters into a single setter. This is the power of using a [Map][7].

   public void setIngredientCount(Ingredient ingredient, int count)
   {
   
      ingredients.put(ingredient, count);
   
   }

MORE IN PART 5

CodePudding user response:

PART 5

This setter can handle all of the different Ingredient values because it takes in an Ingredient parameter. What this means is that, if I want to set the number of Apples to be 12, all I need to do is this -- setIngredientCount(Ingredient.APPLE, 12);. As you can see, you don't need a separate method to handle setting Cherry or Carrot. They can all be set by using this method, it just requires you to specify which ingredient you are setting in the parameter.

Now, once we replace all those setters with this new one, more stuff starts breaking that depended on the old setter. In this case, the ShowRecipesNew constructor (the one with many parameters) is now broken, since it used to use the old setters. We just need to swap out the old setters with the new one.

   public ShowRecipesNew(int apples, int cherries, int carrots, int flour, int sugar) {
      System.out.println("overloaded");
      this();
      setIngredientCount(Ingredient.APPLE, apples);
      setIngredientCount(Ingredient.CHERRY, cherries);
      setIngredientCount(Ingredient.CARROT, carrots);
      setIngredientCount(Ingredient.FLOUR, flour);
      setIngredientCount(Ingredient.SUGAR, sugar);
   }

That this() method actually calls the ShowRecipes() constructor first. That is helpful since it sets all values to the defaultValue first. Then, we can later modify them using the setIngredientCount() method. This way, we can avoid any pesky and annoying nulls.

Once that compiles, then this constructor is good. The next error comes from the getters of all the different ingredients. Just like the setter, we can combine all of this into a single method. Here it is.

   public int getIngredientCount(Ingredient ingredient)
   {
   
      return ingredients.get(ingredient);
   
   }

Once that getter is fixed, then the next method to break is the compareRecipe() method in ShowRecipe class.

Now this method is yet another place we can use enums to make the code both easier to understand and safer. We are actually going to uproot this method almost entirely, and place the logic into the Recipe enum.

Let's start by adding the ability to put a map of requiredIngredients inside of each method, using an EnumMap<K>.

import java.util.EnumMap;
import java.util.Map;

public enum Recipe
{

   APPLE_JAM,
   APPLE_JELLY,
   APPLE_SMOOTHIE,
   APPLE_TART,
   APPLE_PIE,
   CHERRY_JAM,
   CHERRY_JELLY,
   CHERRY_PIE,
   CHERRY_TART,
   CARROT_JUICE,
   CARROT_CAKE,
   CARROT_POTAGE,
   ;
   
   private final EnumMap<Ingredient, Integer> requiredIngredients;
   
   Recipe(Map<Ingredient, Integer> parameter)
   {
   
      requiredIngredients = new EnumMap<>(parameter);
   
   }

}

Now, we can plug in how many of each ingredient we need in order to satisfy this recipe. As for the methods I am using, I am constructing key value pairs as mentioned before, with the key being the ingredient, and the value being how much of that ingredient. I know the methods might look a bit confusing, but that is all that they are doing.

import java.util.EnumMap;
import java.util.Map;

public enum Recipe
{

   APPLE_JAM(
      Map.of(
         Ingredient.APPLE, 3
      )
   ),
   
   APPLE_JELLY(
      Map.of(
         Ingredient.APPLE, 2
      )
   ),
   
   APPLE_SMOOTHIE(
      Map.of(
         Ingredient.APPLE, 2
      )
   ),
   
   APPLE_TART(
      Map.of(
         Ingredient.APPLE, 1,
         Ingredient.FLOUR, 1
      )
   ),
   
   APPLE_PIE(
      Map.of(
         Ingredient.APPLE, 2,
         Ingredient.FLOUR, 1,
         Ingredient.SUGAR, 2
      )
   ),
   
   CHERRY_JAM(
      Map.of(
         Ingredient.CHERRY, 3
      )
   ),
   
   CHERRY_JELLY(
      Map.of(
         Ingredient.CHERRY, 2
      )
   ),
   
   CHERRY_PIE(
      Map.of(
         Ingredient.CHERRY, 2,
         Ingredient.FLOUR, 3,
         Ingredient.SUGAR, 2
      )
   ),
   
   CHERRY_TART(
      Map.of(
         Ingredient.CHERRY, 1,
         Ingredient.FLOUR, 1,
         Ingredient.SUGAR, 1
      )
   ),
   
   CARROT_JUICE(
      Map.of(
         Ingredient.CARROT, 2
      )
   ),
   
   CARROT_CAKE(
      Map.of(
         Ingredient.CARROT, 1,
         Ingredient.FLOUR, 1,
         Ingredient.SUGAR, 1
      )
   ),
   
   CARROT_POTAGE(
      Map.of(
         Ingredient.CARROT, 2,
         Ingredient.FLOUR, 1
      )
   ),
   ;
   
   private final EnumMap<Ingredient, Integer> requiredIngredients;
   
   Recipe(Map<Ingredient, Integer> parameter)
   {
   
      requiredIngredients = new EnumMap<>(parameter);
   
   }

}

As you can see, the code is a lot more explicit now. Instead of saying we need a value of 3 in ingredients[0] to satisfy recipeNamesA[0], we can now say we need a value of 3 for Ingredient.APPLE to satisfy APPLE_PIE. We are using more complex terms to do it, but once you understand those complex terms, the solution becomes much more expressive and easy to understand.

And finally, let's add a method to Recipe in order to help us do the quantity check.

   public boolean canMakeRecipeWith(Map<Ingredient, Integer> ingredientsWeHave)
   {
   
      for (Map.Entry<Ingredient, Integer> each : requiredIngredients.entrySet())
      {
      
         Ingredient ingredient = each.getKey();
         int howMuchWeHave = ingredientsWeHave.get(ingredient);
         int howMuchWeNeed = requiredIngredients.get(ingredient);
      
         if (howMuchWeHave < howMuchWeNeed)
         {
         
            return false;
         
         }
      
      }
      
      return true;
   
   }

This method will allow us to take in our Map<Ingredient, Integer> and see if we have enough ingredients to make the chosen recipe. The way we will chose the recipe is by doing something like this. Recipe.APPLE_PIE.canMakeRecipeWith(ingredients) -- this will return true or false depending on whether or not ingredients has enough of each Ingredient to make an APPLE_PIE

Now, we have modified this enum to become powerful and expressive. Let's start using it.

Back to our original method compareRecipe(), we can pretty much rework this method from the ground up to use our new enum functionality.

   public void compareRecipe(UserPrompt prompt) {
   
      prompt.print("According to the input, you can make:");
      
      if (getIngredientCount(Ingredient.APPLE) == 0) {
         prompt.print("No apple recipes");
      }
   
      if (getIngredientCount(Ingredient.CHERRY) == 0) {
         prompt.print("No cherry recipes");
      }
   
      if (getIngredientCount(Ingredient.CARROT) == 0) {
         prompt.print("No carrot recipes");
      }
     
      for (Recipe each : Recipe.values())
      {
      
         if (each.canMakeRecipeWith(ingredients))
         {
         
            prompt.print(each.ordinal()   " "   each.name());
         
         }
      
      }
   
   }

This is really helpful to us because now, if we ever add a new Recipe, we never have to touch this method again. Since we plugged in the Recipe enum into ShowRecipesNew, we only have to modify Recipe in order to make sweeping changes for ShowRecipesNew.

In fact, we can even go a step farther, and change the way we do the no ____ recipes feature too.

   public void compareRecipe(UserPrompt prompt) {
   
      prompt.print("According to the input, you can make:");
      
      for (Ingredient each : Ingredient.values())
      {
      
         Integer count = getIngredientCount(each);
      
         if (count == 0)
         {
         
            prompt.print("No "   each.name().toLowerCase()   " recipes");
         
         }
      
      }
      
      for (Recipe each : Recipe.values())
      {
      
         if (each.canMakeRecipeWith(ingredients))
         {
         
            prompt.print(each.ordinal()   " "   each.name());
         
         }
      
      }
   
   }

Now, if you ever add a new value to Ingredient, you don't need to add another method to notify that there aren't enough ingredients. This loop will now handle that for you.

As for what this method does, it basically does exactly what it did before, but using enums. It uses the ordinal() method to print the number, then uses the name() method to print the name. Each enum has an ordinal, which is just like an index in the array example we used to have before. We will be using arrays a little bit in our solution, but they will be Ingredient[] now instead of int[] or String[]. That will come later on though.

Now, if we try compiling, both ShowRecipesNew and Recipe should be good. Of course, we still have plenty to do in ShowRecipesNew.

First, let's start deleting the non-enum stuff. That includes the leftover Scanner (which should have been deleted long ago), the ingredientNames array, and all the getters and setters for the array instance fields. Once we clean up ShowRecipesNew, here is what we have left.

ShowRecipesNew.java

import java.util.EnumMap;
import java.util.Map;
import java.util.Scanner;

//object class
public class ShowRecipesNew {

 //ingredients array - setters will set
   private EnumMap<Ingredient, Integer> ingredients = new EnumMap<>(Ingredient.class);

 //Basic constructor
   public ShowRecipesNew() {
      for (Ingredient each : Ingredient.values())
      {
         ingredients.put(each, each.defaultValue());
      }
   }

 //overloaded constructor that covers each field
   public ShowRecipesNew(int apples, int cherries, int carrots, int flour, int sugar) {
      this();
      System.out.println("overloaded");
      setIngredientCount(Ingredient.APPLE, apples);
      setIngredientCount(Ingredient.CHERRY, cherries);
      setIngredientCount(Ingredient.CARROT, carrots);
      setIngredientCount(Ingredient.FLOUR, flour);
      setIngredientCount(Ingredient.SUGAR, sugar);
   }

   public void IngredientCount(UserPrompt prompt) { //Lists ingredient input counts
      prompt.print("According to your input, you have:");
   
      prompt.print("Ingredient\tValue");
      for(Map.Entry<Ingredient, Integer> each : ingredients.entrySet()) {
         prompt.print(each.getKey()   "\t\t"   each.getValue());
      }//end for loop
   }//end ingredient listing
 
   public void setIngredientCount(Ingredient ingredient, int count)
   {
   
      ingredients.put(ingredient, count);
   
   }
 
   public int getIngredientCount(Ingredient ingredient)
   {
   
      return ingredients.get(ingredient);
   
   }
 
   public void compareRecipe(UserPrompt prompt) {
   
      prompt.print("According to the input, you can make:");
      
      for (Ingredient each : Ingredient.values())
      {
      
         Integer count = getIngredientCount(each);
      
         if (count == 0)
         {
         
            prompt.print("No "   each.name().toLowerCase()   " recipes");
         
         }
      
      }
      
      for (Recipe each : Recipe.values())
      {
      
         if (each.canMakeRecipeWith(ingredients))
         {
         
            prompt.print(each.ordinal()   " "   each.name());
         
         }
      
      }
   
   }

}

Much smaller and more compact. Now, there are no more references to arrays or magic numbers. Just pure values and enums. I also went ahead and removed the getter getIngredientsBag() for ingredients. Even though it returns an EnumMap<Ingredient, Integer>, we can check each Ingredient individually using getIngredientCount(), so there's not much benefit in exposing the whole map.

Now, if we compile all of our classes, everything should be working except for Driver.java. So let's start fixing that.

Now, we could go through each one of the errors individually, but just trust me when I say we should just go through each method and rework it to use enums instead. Java has decent error messages, but sometimes (and in this particular moment), they will send you off on a goose hunt when the real issue is elsewhere. So, let's go through the methods in Driver one by one and switch out the int[] and String[] based solutions for enum ones.

  • public Driver(ShowRecipesNew parameter) - nothing to change here, this is perfect.
  • public static void main (String [] args) - same thing here, no int[] or String[], so we are all good.
  • public void selection(UserPrompt prompt) - this is fine. All we are doing is letting the user choose which option they want. Technically, we could turn this into an enum too, but that would be excessive with no short term benefit.
  • public void WantToCook(UserPrompt prompt) - Ok, here is something that we do need to change. In this method, we are manually editing the values via the old getters and setters. Obviously those are gone now, and more importantly, we have a much more efficient way of doing this. But in order to use it, we must modify our Recipe enum one more time.

In Recipe, we created the method canMakeRecipeWith, whose only job was to check if we had enough of each Ingredient to be able to cook our recipe. However, there is no method to actually modify the ingredients counts. Let's give Recipe the ability to do that by plugging ShowRecipesNew into a new method in Recipe.

   public String cookRecipeWith(ShowRecipesNew sr)
   {
   
      for (Map.Entry<Ingredient, Integer> each : requiredIngredients.entrySet())
      {
      
         Ingredient ingredient = each.getKey();
         int currentNum = sr.getIngredientCount(ingredient);
         int requiredAmount = requiredIngredients.get(ingredient);
      
         sr.setIngredientCount(ingredient, currentNum - requiredAmount);
      
      }
      
      return name();
   
   }

Of course, it is not the job of Recipe to do state manipulation, but we need state manipulation to occur, so we plug in ShowRecipesNew so that we can delegate state manipulation.

Now, what this method does is go through the requiredIngredients of the Recipe, then removes the amount from sr. Basically, it is doing exactly what WantToCook() was doing, but it's using a for loop to make this code both smaller and safer.

Now, let's rework WantToCook() to use this new functionality we created.

   public void WantToCook(UserPrompt prompt) { //Enter recipe number - deducts ingredients
     
      int recipeNum = prompt.askForNumber("What would you like to cook? Please enter the recipe number.");
      
      if (recipeNum >= Recipe.values().length)
      {
      
         prompt.print("invalid entry");
      
      }
      
      else
      {
      
         final Recipe recipeToMake = Recipe.values()[recipeNum];
      
         prompt.print(recipeToMake.cookRecipeWith(sr));
      
      }
      
      prompt.print("You have cooked a thing! Good job!");
      CookAgain(prompt); //cook again? prompt
   } //end WantToCook method

Now, all the logic is being handled by the Recipe enum, and we don't need to think about whether we caught all the values or not - the for loop is handling that for us. Let's move on to the next method.

  • public void input(UserPrompt prompt) - This one also needs to be reworked. Let's start on this one too.
   public void input(UserPrompt prompt) { // user inputs ingredient counts
   
     //Asks user for ingredients on-hand
      prompt.print("Please enter how many of each ingredient you have:");
      
      for (Ingredient each : Ingredient.values())
      {
      
         int count = prompt.askForNumber(each.name());
      
         sr.setIngredientCount(each, count);
      
      }
   
   }//end user ingredient input

This is stuff we've seen a few times before, so I won't go in depth - we cycle through all possible values of Ingredient, then use sr to set them to the count number that the user entered.

  • public void CookAgain(UserPrompt prompt) - This one also needs to change, but we will need to undo some of the work we have done prior. It will be very minimal though, so don't worry.

First, what this method does is ask the user what they want to do. Then, if the user says they want to cook, they check if cooking can be done. If so, then reenter the cooking process again. But if not, then notify the user. If the user says they do not want to cook, then we just break out and more or less end the method.

In retrospect, this method is a little more complex than I remember it, but we can clean it up a lot with only a little effort.

First, we need to check if we have enough Ingredient in sr to be able to cook any of the Recipe. In Recipe, we have a method that allows us to check if we have enough Ingredient, but it checks a Map, not ShowRecipesNew, which is what sr is. Normally, we would just make a new method to handle checking, but since what they do is exactly the same with only a different parameter, I think it makes more sense to just have one method handle checking.

So now, let's redo our canMakeRecipeWith() method in Recipe.

   public boolean canMakeRecipeWith(ShowRecipesNew sr)
   {
   
      for (Map.Entry<Ingredient, Integer> each : requiredIngredients.entrySet())
      {
      
         Ingredient ingredient = each.getKey();
         int howMuchWeHave = sr.getIngredientCount(ingredient);
         int howMuchWeNeed = requiredIngredients.get(ingredient);
      
         if (howMuchWeHave < howMuchWeNeed)
         {
         
            return false;
         
         }
      
      }
      
      return true;
   
   }

Nice, now our cookRecipeWith and canMakeRecipeWith methods both have ShowRecipesNew sr as their parameter. That hopefully makes things a bit simpler to follow along with.

However, changing this method broke ShowRecipesNew a little, so let's fix that. Specifically, it broke the compareRecipe() method. It's a simple fix though.

   public void compareRecipe(UserPrompt prompt) {
   
      prompt.print("According to the input, you can make:");
      
      for (Ingredient each : Ingredient.values())
      {
      
         Integer count = getIngredientCount(each);
      
         if (count == null || count == 0)
         {
         
            prompt.print("No "   each.name().toLowerCase()   " recipes");
         
         }
      
      }
      
      for (Recipe each : Recipe.values())
      {
         //here is the change
         if (each.canMakeRecipeWith(this))
         {
         
            prompt.print(each.ordinal()   " "   each.name());
         
         }
      
      }
   
   }

The keyword this is used in Java to represent the instance itself. It's more or less saying use myself as the parameter for this method. And that works because canMakeRecipeWith() is looking for a ShowRecipesNew parameter. Therefore, if ShowRecipesNew submits itself as the parameter, everything checks out fine!

Anyways, now ShowRecipesNew is working again, so we can go back to Driver. However, before we go back to our method, there is one logic bug I would like to fix. In WantToCook(), I would like to check whether the Recipe the user selected is actually something we have enough Ingredient to make. So, I will add a simple check to that method.

   public void WantToCook(UserPrompt prompt) { //Enter recipe number - deducts ingredients
     
      int recipeNum = prompt.askForNumber("What would you like to cook? Please enter the recipe number.");
      
      if (recipeNum >= Recipe.values().length || recipeNum < 0)
      {
      
         prompt.print("invalid entry");
      
      }
      
      else
      {
      
         final Recipe recipeToMake = Recipe.values()[recipeNum];
      
         if (recipeToMake.canMakeWith(sr))
         {
         
            prompt.print(recipeToMake.cookRecipeWith(sr));
         
         }
         
         else
         {
         
            prompt.print("not enough ingredients");
         
         }
      
      }
      
      prompt.print("You have cooked a thing! Good job!");
      CookAgain(prompt); //cook again? prompt
   } //end WantToCook method

Nice, now we have fixed that logic error, we can go back to the method we were editing, CookAgain.

For this method, we need to check if we have enough Ingredient to make any of the Recipe we have. To do this, let's just cycle through all of the Recipe and see if we have enough Ingredient to make any of them. And to keep things organized, I'll make a new method to do this cycling, and then toss it onto the Recipe enum.

   public static boolean canCookAnything(ShowRecipesNew sr)
   {
   
      for (Recipe each : Recipe.values())
      {
      
         if (each.canMakeRecipeWith(sr))
         {
         
            return true;
         
         }
      
      }
      
      return false;
      
   }

This works almost exactly the same as the other canMakeRecipeWith() method, but the difference being, it borrows that method to check all possible values in Recipe.

Now, let's use that new method in our CookAgain() method.

   public void CookAgain(UserPrompt prompt) {   
   
      int answer = prompt.askForNumber("Would you like to cook again? 1 = yes, 2 = no - ");
   
      switch (answer) {
      
         case 1: //which means yes
            if (Recipe.canCookAnything(sr)) {
               prompt.print("You've used some ingredients. Let's see what you have now.");
               sr.IngredientCount(prompt);
               sr.compareRecipe(prompt);
               WantToCook(prompt);
               break;
            }
            else {
               prompt.print("You don't have enough ingredients left to cook anything.");
               break;
            }
         case 2: //which means no
            prompt.print("Ok, done cooking. Enjoy!");
            break;
         default:
            prompt.print("Invalid answer please try again!");
      
      }// End answer switch
   
   }

And just like that, we have simplified this method down to check all possible values of Recipe and Ingredient, not just the apples, cherries, and carrots it was checking before.

And now, we have finally finished the refactor of your entire program!

COMPLETE

If you made it this far, be proud of yourself - you have proven to be a resilient programmer willing to do a deep dive to learn more. You will certainly go far in this industry, I don't doubt that.

I will post the completed program in PART FINAL, since I am running out of space here.

CodePudding user response:

PART FINAL

As promised, here is the complete program, with everything working using all of the refactored code.

Driver.java

public class Driver
{

   private ShowRecipesNew sr;
   
   public Driver(ShowRecipesNew parameter)
   {
   
      sr = parameter;
   
   }

   public static void main (String [] args){
      final ShowRecipesNew first = new ShowRecipesNew();
      System.out.println("FIRST");
      
      final UserPrompt prompt = new UserPrompt();
      
      final Driver driver = new Driver(first);
      driver.input(prompt);
      first.compareRecipe(prompt);
      driver.WantToCook(prompt);
   
      driver.selection(prompt);
        
   }
      
   public void selection(UserPrompt prompt){ //Can access every method in class except CookAgain
     
     //1. Cook something -- WantToCook()
     //2. See ingredient list -- IngredientCount()
     //3. See available recipes -- compareRecipe()
     //4. Modify total ingredient amounts -- input()
     //maybe  put this in ShowRecipes, create object for each to put in own classes
     
      int userChoice = 
         prompt.askForNumber(
            "What would you like to do now? Enter a number to select. \n1. Cook something \n2. See ingredients \n3. See available recipes \n4. Modify ingredient amounts"
         );
     
      switch (userChoice) {
         case 1:
            WantToCook(prompt);
            break;
         case 2: 
            sr.IngredientCount(prompt);
            break;
         case 3: 
            sr.compareRecipe(prompt);
            break;
         case 4:
            input(prompt);
            break;
         default:
            System.out.println("Invalid");
            return;
      }
      
   }

   public void WantToCook(UserPrompt prompt) { //Enter recipe number - deducts ingredients
     
      int recipeNum = prompt.askForNumber("What would you like to cook? Please enter the recipe number.");
      
      if (recipeNum >= Recipe.values().length || recipeNum < 0)
      {
      
         prompt.print("invalid entry");
      
      }
      
      else
      {
      
         final Recipe recipeToMake = Recipe.values()[recipeNum];
      
         if (recipeToMake.canMakeRecipeWith(sr))
         {
         
            prompt.print(recipeToMake.cookRecipeWith(sr));
         
         }
         
         else
         {
         
            prompt.print("not enough ingredients");
         
         }
      
      }
      
      prompt.print("You have cooked a thing! Good job!");
      CookAgain(prompt); //cook again? prompt
   } //end WantToCook method

   public void input(UserPrompt prompt) { // user inputs ingredient counts
   
     //Asks user for ingredients on-hand
      prompt.print("Please enter how many of each ingredient you have:");
      
      for (Ingredient each : Ingredient.values())
      {
      
         int count = prompt.askForNumber(each.name());
      
         sr.setIngredientCount(each, count);
      
      }
   
   }//end user ingredient input

   public void CookAgain(UserPrompt prompt) {   
   
      int answer = prompt.askForNumber("Would you like to cook again? 1 = yes, 2 = no - ");
   
      switch (answer) {
      
         case 1: //which means yes
            if (Recipe.canCookAnything(sr)) {
               prompt.print("You've used some ingredients. Let's see what you have now.");
               sr.IngredientCount(prompt);
               sr.compareRecipe(prompt);
               WantToCook(prompt);
               break;
            }
            else {
               prompt.print("You don't have enough ingredients left to cook anything.");
               break;
            }
         case 2: //which means no
            prompt.print("Ok, done cooking. Enjoy!");
            break;
         default:
            prompt.print("Invalid answer please try again!");
      
      }// End answer switch
   
   }
   
}

UserPrompt.java

import java.util.Scanner;

public class UserPrompt
{

   private final Scanner kbd = new Scanner(System.in);

   public int askForNumber(String prompt)
   {
   
      //Print your prompt to the command line -- I added a little extra at the end for simple reading
      System.out.print(prompt   " - ");
      
      return kbd.nextInt();
      
   }
   
   public void print(String message)
   {
   
      System.out.println(message);
   
   }

}

ShowRecipesNew.java

import java.util.EnumMap;
import java.util.Map;
import java.util.Scanner;

//object class
public class ShowRecipesNew {

 //ingredients array - setters will set
   private EnumMap<Ingredient, Integer> ingredients = new EnumMap<>(Ingredient.class);

 //Basic constructor
   public ShowRecipesNew() {
      for (Ingredient each : Ingredient.values())
      {
         ingredients.put(each, each.defaultValue());
      }
   }

 //overloaded constructor that covers each field
   public ShowRecipesNew(int apples, int cherries, int carrots, int flour, int sugar) {
      this();
      System.out.println("overloaded");
      setIngredientCount(Ingredient.APPLE, apples);
      setIngredientCount(Ingredient.CHERRY, cherries);
      setIngredientCount(Ingredient.CARROT, carrots);
      setIngredientCount(Ingredient.FLOUR, flour);
      setIngredientCount(Ingredient.SUGAR, sugar);
   }


   public void IngredientCount(UserPrompt prompt) { //Lists ingredient input counts
      prompt.print("According to your input, you have:");
   
      prompt.print("Ingredient\tValue");
      for(Map.Entry<Ingredient, Integer> each : ingredients.entrySet()) {
         prompt.print(each.getKey()   "\t\t"   each.getValue());
      }//end for loop
   }//end ingredient listing
 
   public void setIngredientCount(Ingredient ingredient, int count)
   {
   
      ingredients.put(ingredient, count);
   
   }
 
   public int getIngredientCount(Ingredient ingredient)
   {
   
      return ingredients.get(ingredient);
   
   }
 
   public void compareRecipe(UserPrompt prompt) {
   
      prompt.print("According to the input, you can make:");
      
      for (Ingredient each : Ingredient.values())
      {
      
         Integer count = getIngredientCount(each);
      
         if (count == null || count == 0)
         {
         
            prompt.print("No "   each.name().toLowerCase()   " recipes");
         
         }
      
      }
      
      for (Recipe each : Recipe.values())
      {
      
         if (each.canMakeRecipeWith(this))
         {
         
            prompt.print(each.ordinal()   " "   each.name());
         
         }
      
      }
   
   }

}

Ingredient.java

public enum Ingredient
{

   APPLE(1),
   CHERRY(1),
   CARROT(1),
   FLOUR(0),
   SUGAR(0),
   ;
   
   private final int defaultValue;
   
   Ingredient(int input)
   {
   
      defaultValue = input;
   
   }
   
   public int defaultValue()
   {
   
      return defaultValue;
   
   }

}

Recipe.java

import java.util.EnumMap;
import java.util.Map;

public enum Recipe
{

   APPLE_JAM(
      Map.of(
         Ingredient.APPLE, 3
      )
   ),
   
   APPLE_JELLY(
      Map.of(
         Ingredient.APPLE, 2
      )
   ),
   
   APPLE_SMOOTHIE(
      Map.of(
         Ingredient.APPLE, 2
      )
   ),
   
   APPLE_TART(
      Map.of(
         Ingredient.APPLE, 1,
         Ingredient.FLOUR, 1
      )
   ),
   
   APPLE_PIE(
      Map.of(
         Ingredient.APPLE, 2,
         Ingredient.FLOUR, 1,
         Ingredient.SUGAR, 2
      )
   ),
   
   CHERRY_JAM(
      Map.of(
         Ingredient.CHERRY, 3
      )
   ),
   
   CHERRY_JELLY(
      Map.of(
         Ingredient.CHERRY, 2
      )
   ),
   
   CHERRY_PIE(
      Map.of(
         Ingredient.CHERRY, 2,
         Ingredient.FLOUR, 3,
         Ingredient.SUGAR, 2
      )
   ),
   
   CHERRY_TART(
      Map.of(
         Ingredient.CHERRY, 1,
         Ingredient.FLOUR, 1,
         Ingredient.SUGAR, 1
      )
   ),
   
   CARROT_JUICE(
      Map.of(
         Ingredient.CARROT, 2
      )
   ),
   
   CARROT_CAKE(
      Map.of(
         Ingredient.CARROT, 1,
         Ingredient.FLOUR, 1,
         Ingredient.SUGAR, 1
      )
   ),
   
   CARROT_POTAGE(
      Map.of(
         Ingredient.CARROT, 2,
         Ingredient.FLOUR, 1
      )
   ),
   ;
   
   private final EnumMap<Ingredient, Integer> requiredIngredients;
   
   Recipe(Map<Ingredient, Integer> parameter)
   {
   
      requiredIngredients = new EnumMap<>(parameter);
   
   }
   
   public boolean canMakeRecipeWith(ShowRecipesNew sr)
   {
   
      for (Map.Entry<Ingredient, Integer> each : requiredIngredients.entrySet())
      {
      
         Ingredient ingredient = each.getKey();
         int howMuchWeHave = sr.getIngredientCount(ingredient);
         int howMuchWeNeed = requiredIngredients.get(ingredient);
      
         if (howMuchWeHave < howMuchWeNeed)
         {
         
            return false;
         
         }
      
      }
      
      return true;
   
   }

   public String cookRecipeWith(ShowRecipesNew sr)
   {
   
      for (Map.Entry<Ingredient, Integer> each : requiredIngredients.entrySet())
      {
      
         Ingredient ingredient = each.getKey();
         int currentNum = sr.getIngredientCount(ingredient);
         int requiredAmount = requiredIngredients.get(ingredient);
      
         sr.setIngredientCount(ingredient, currentNum - requiredAmount);
      
      }
      
      return name();
   
   }
   
   public static boolean canCookAnything(ShowRecipesNew sr)
   {
   
      for (Recipe each : Recipe.values())
      {
      
         if (each.canMakeRecipeWith(sr))
         {
         
            return true;
         
         }
      
      }
      
      return false;
      
   }

}
  • Related