Goal: To understand the significance of design patterns, and to apply the Composite Pattern to the recipe application's hierarhical structure.
Tutorial
When working on an application, a particular design issue might arise. How to solve such-and-such problem? You might attempt to solve it yourself, but perhaps others - working in teams - have already faced such a problem and know a state-of-the-art solution. Although your application might be totally new, most applications face certain recurring problems. A Design Pattern is a standard architecture to solve a specific type of problem. In this sense, software becomes re-usable.
The COMPOSITE PATTERN provides a convenient way to structure a hierarchy, and then to iterate throughout the hierarchy and apply the same operation to every element of the hierarchy.
In our recipe application, MenuBook has many FoodMenus, which have many Recipes, which have many Ingredients, which have an Item with a specific number of calories. We want to accumulate all of the getCalories() operation calls in order to determine the entire number of calories in the MenuBook. This problem has a solution: The Composite Design Pattern.
Before applying the pattern to our recipe application, let us examine it in more detail: ex7b.pdf
The UML in the above .pdf file, is implemented in CompositePattern.java, and run with this demo.
Assignment
Now we are ready to implement this pattern for our own recipe application.
In directory planner/gui/domain, code interface FoodElement.java with a long getCalories() method.
Code FoodComposite.java using CompositePattern.java as a reference source.
Recall the UML design for the classes: ex7a.pdf
Note that MenuBook, FoodMenu, Recipe each are parents of children in a 1..* relationship.
That is, each of MenuBook, FoodMenu, Recipe "is a" FoodComposite.
Each of the children, Ingredient, Item, "is a" FoodElement.
The purpose of the Composite Pattern is to implement this hierarchical structure. Make the necessary changes to your classes to satisfy these relationships.
Besides Recipe having many Ingredients, Recipe also has many Steps. You will have to handle this as a special case: in Recipe, add another ArrayList to contain <Step>. Also, code add() and remove() for steps.
More notes on this special case. Recipe has many Ingredients and has many Items. And because Recipe has these two sets, it needs a special case to handle the second set, i.e. Steps.
But it already has the one for Ingredients naturally because Recipe "is a" FoodComposite. And FoodComposite has it's own ArrayList called elements. And that will store the Ingredients for the Recipe, inside the parent class.
So all the FoodComposites inherit the list, and the iterator code. Just Recipe needs an extra one for Steps.
Ex7b-Test1
Compile your code and inside of planner/gui/domain/MenuBook.java uncomment the code for Ex7b-Test1 in the main():
System.out.println("Total Calories: " + mb.getCalories());
add(m1); m1.add(r1); r1.add(ing1); r1.add(ing2); r1.add(ing3); r1.add(new Step("Grill hamburger meat for 10 minutes")); r1.add(new Step("After 5 minutes, place on cheese on hamburger")); r1.add(new Step("After 7 minutes, place top/bottom of bun on grill")); r1.add(new Step("When done, serve hamburger in bun"));
Note that the above code builds the hierarchy by adding children to parents.
Run your code:
java MenuBook
Ex7b-Test1 Output
Total Calories: 950
Did you get the correct result?
The next stage is to implement toString().
In FoodComposite, code a toString() that accumulates all of the individual Elements. Be sure to add a carriage return ("\n") at that end of each one.
Any child that inherits from FoodComposite, can get a full picture of the list stored inside of FoodComposite by just saying: super.toString().
The Composites MenuBook, Recipe, FoodMenu, each need a toString(). But they have already inherited the toString() that you coded above in FoodComposite.
Recall Ex7a specified the desired output and some comments have been added:
MENU BOOK: // MenuBook's toString() should prepend Menu: Birthday Dinner (DINNER) // Menu's toString() should prepend Recipe: Hamburgers // Recipe's toString() should prepend Ingredients: // Recipe should do this 0.25 lb Hamburger Meat // this iteration will come out naturally from super.toString() 1.0 regular Hamburger Bun // Ingredient's toString() needs to form a particular line here 1.0 slice American Cheese Steps: // Recipe should do this 1. Grill hamburger meat for 10 minutes // and iterate thru all Steps 2. After 5 minutes, place on cheese on hamburger // Step's toString() should just return the text 3. After 7 minutes, place top/bottom of bun on grill 4. When done, serve hamburger in bun Total Calories: 950 // MenuBook's toString() should append
Be careful to use super.toString() in the appropriate places to get FoodComposites accumulating toString().
Ex7b-Test2
In planner/gui/domain/MenuBook, uncomment the code for Ex7b-Test2 in the main():
System.out.println(mb.toString());
Compile and run.
Ex7b-Test2 Output
Does it print the above MENU BOOK?
Now for the last part of the assignment.
FoodMenu, Recipe, Item, Unit should each have a public static HashMap; recall HashMap from Ex6. Be sure the instance variable in all classes is called "hashMap" so Ex7b-Test3 below will compile.
What generic should be stored in the HashMap in FoodMenu? Itself! That is, <String, FoodMenu>, where String will be the menuName. And be sure that this object actually gets stored into the HashMap.
What is the purpose of this? Well, the GUI in Ex7c will need direct access to a list (or table) of FoodMenus. Likewise, for Recipe, Item, Unit. How does menuName get into the HashMap? At construction.
Ex7b-Test3
Uncomment the code for Ex7b-Test3 in the constructor which prints out the keys to the HashMaps. Compile and run again.
Ex7b-Test3 Output
Keys to HashMaps ---------------------- FoodMenu: Birthday Dinner Recipe: Hamburgers Item: American Cheese Hamburger Meat Sugar Hamburger Bun Unit: oz cup regular ten slice lb ----------------------
Did you get the correct results? Note that hash tables are not sorted by keys. So you might get a different ordering, that's OK. Your version of Java is different than the version that produced the above. Your version may have different hash function and/or size.
Turn-In: All 8 Domain Objects (or entire directory: planner), including in MenuBook a commented-out listing of resultant output. Also, submit FoodElement, FoodComposite.