0% found this document useful (0 votes)
24 views71 pages

09 - Iterator & Composite Patterns

The document discusses using the Iterator pattern to allow a Java-enabled waitress to print menus from different restaurants in a consistent way. It describes how the waitress currently needs separate code to handle different menu implementations. The pattern is proposed to encapsulate the iteration through each menu's items through a common Iterator interface to simplify the waitress code.

Uploaded by

Yusuf Taşoğlu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
24 views71 pages

09 - Iterator & Composite Patterns

The document discusses using the Iterator pattern to allow a Java-enabled waitress to print menus from different restaurants in a consistent way. It describes how the waitress currently needs separate code to handle different menu implementations. The pattern is proposed to encapsulate the iteration through each menu's items through a common Iterator interface to simplify the waitress code.

Uploaded by

Yusuf Taşoğlu
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 71

CSE351 DESIGN PATTERNS

09. ITERATOR AND COMPOSITE


PATTERNS
Well-Managed Collections
There are lots of ways to stuff
objects into a collection.
Breaking News:
Objectville Diner and
Objectville Pancake House
Merge
But there is a slight problem…

2
public class MenuItem {
String name;
String description;
Check out the Menu Items
boolean vegetarian; At least Lou and Mel agree on the
double price;
implementation of the MenuItems.
public MenuItem(String name, String description,
boolean vegetarian, double price)
{
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public double getPrice() {
return price;
}
public boolean isVegetarian() {
return vegetarian;
}
}

3
Lou’s Menu implementation
public class PancakeHouseMenu implements Menu {
ArrayList menuItems;

public PancakeHouseMenu() {
menuItems = new ArrayList();

addItem(“K&B’s Pancake Breakfast”,


“Pancakes with scrambled eggs, and toast”, true, 2.99);

addItem(“Regular Pancake Breakfast”,


“Pancakes with fried eggs, sausage”, false, 2.99);

addItem(“Blueberry Pancakes”,
“Pancakes made with fresh blueberries”, true, 3.49);

addItem(“Waffles”,
“Waffles, with your choice of blueberries or strawberries”, true, 3.59);
}
public void addItem(String name, String description,
boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.add(menuItem);
}
public ArrayList getMenuItems() {
return menuItems;
}
// other menu methods here
} 4
public class DinerMenu implements Menu {
static final int MAX_ITEMS = 6;
and Mel’s
int numberOfItems = 0;
MenuItem[] menuItems;

public DinerMenu() {
menuItems = new MenuItem[MAX_ITEMS];
addItem(“Vegetarian BLT”, “(Fakin’) Bacon with lettuce & tomato on whole wheat”, true, 2.99);
addItem(“BLT”, “Bacon with lettuce & tomato on whole wheat”, false, 2.99);
addItem(“Soup of the day”, “Soup of the day, with a side of potato salad”, false, 3.29);
addItem(“Hotdog”, “A hot dog, with saurkraut, relish, onions, topped with cheese”, false, 3.05);
// a couple of other Diner Menu items added here
}
public void addItem(String name, String description, boolean vegetarian, double price)
{
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
if (numberOfItems >= MAX_ITEMS) {
System.err.println(“Sorry, menu is full! Can’t add item to menu”);
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems = numberOfItems + 1;
}
}
public MenuItem[] getMenuItems() {
return menuItems;
}
// other menu methods here
}
5
What’s the problem with having two different
menu representations?
Imagine the Java-enabled
waitress specifies that The Java-Enabled Waitress Specification
▪ she can print a custom
menu for customers on
demand,
▪ and even tell you if a
menu item is vegetarian
without having to ask
the cook
now that’s an innovation!

6
Let’s start by stepping through how we’d implement the printMenu() method:
1. To print all the items on each menu, you’ll need to call the getMenuItem()
method on the PancakeHouseMenu and the DinerMenu to retrieve their
respective menu items. Note that each returns a different type:
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
ArrayList breakfastItems = pancakeHouseMenu.getMenuItems();

DinerMenu dinerMenu = new DinerMenu();


MenuItem[] lunchItems = dinerMenu.getMenuItems();

3. Implementing every other


2. Now, to print out the items from the PancakeHouseMenu, we’ll loop through method in the Waitress is
the items on the breakfastItems ArrayList. And to print out the Diner items going to be a variation of this
we’ll loop through the Array. theme. We’re always going to
for (int i = 0; i < breakfastItems.size(); i++) { need to get both menus and
MenuItem menuItem = (MenuItem)breakfastItems.get(i);
System.out.print(menuItem.getName() + “ “); use two loops to iterate
System.out.println(menuItem.getPrice() + “ “); through their items.
System.out.println(menuItem.getDescription());
}
If another restaurant with a
for (int i = 0; i < lunchItems.length; i++) { different implementation is
MenuItem menuItem = lunchItems[i]; acquired then we’ll have three
System.out.print(menuItem.getName() + “ “);
System.out.println(menuItem.getPrice() + “ “); loops.
System.out.println(menuItem.getDescription());
} 7
Sharpen your pencil
Based on our implementation of printMenu(), which of the following apply?
A. We are coding to the PancakeHouseMenu and DinerMenu concrete implementations, not to an
interface.
B. The Waitress doesn’t implement the Java Waitress API and so she isn’t adhering to a standard.
C. If we decided to switch from using DinerMenu to another type of menu that implemented its list
of menu items with a Hashtable, we’d have to modify a lot of code in the Waitress.
D. The Waitress needs to know how each menu represents its internal collection of menu items;
this violates encapsulation.
E. We have duplicate code: the printMenu() method needs two separate loops to iterate over the
two different kinds of menus. And if we added a third menu, we’d have yet another loop.
F. The implementation isn’t based on MXML (Menu XML) and so isn’t as interoperable as it should
be.

8
W Our situation is:
▪ Mel and Lou don’t want to change their implementations because it would mean
H rewriting a lot of code that is in each respective menu class.
▪ But if one of them doesn’t give in, then we’re going to have the job of implementing a
A Waitress that is going to be hard to maintain and extend.

T What would be nice?


▪ if we could find a way to allow them to implement the same interface for their menus
(they’re already close, except for the return type of the getMenuItems() method).
▪ That way we can minimize the concrete references in the Waitress code and also hopefully get
N rid of the multiple loops required to iterate over both menus.
Sound good? Well, how are we going to do that?
O Can we encapsulate the iteration?
What is changing here?
W If we’ve learned one thing in this class, it’s for sure:
the iteration caused by different collections of
ENCAPSULATE WHAT VARIES
objects being returned from the menus.
?
9
1. To iterate through the breakfast items we use the size() and get()
methods on the ArrayList:
for (int i = 0; i < breakfastItems.size(); i++) {
MenuItem menuItem = (MenuItem)breakfastItems.get(i);
}

for (int i = 0; i < lunchItems.length; i++) {


MenuItem menuItem = lunchItems[i];
}

2. And to iterate through the lunch items we


use the Array length field and the array
subscript notation on the MenuItem Array.
10
3. Now what if we create an object, let’s call it an ITERATOR, that
encapsulates the way we iterate through a collection of objects?
Let’s try this on the ArrayList

Iterator iterator = breakfastMenu.createIterator();

while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem)iterator.next();
}

11
4. Let’s try it on the Array too:

Iterator iterator = lunchMenu.createIterator();

while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem)iterator.next();
}

12
Meet the Iterator Pattern
The first thing you need to know about the
Iterator Pattern is that it relies on an
interface called Iterator. Here’s one possible
Iterator interface:

Now, once we have this interface,


we can implement Iterators for
any kind of collection of objects:
arrays, lists, hashtables, ...
Let’s implement this Iterator...
13
Adding an Iterator to DinerMenu
To add an Iterator to the DinerMenu we And now we need to implement a concrete Iterator that
first need to define the Iterator Interface: works for the DinerMenu:
public interface Iterator { public class DinerMenuIterator implements Iterator {
MenuItem[] items;
boolean hasNext();
int position = 0;
Object next(); public DinerMenuIterator(MenuItem[] items) {
} this.items = items;
}
public Object next() {
MenuItem menuItem = items[position];
position = position + 1;
return menuItem;
}
public boolean hasNext() {
if (position >= items.length || items[position] == null)
return false;
else
return true;
}
}

14
Reworking the Diner Menu with Iterator
public class DinerMenu implements Menu {
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
MenuItem[] menuItems;
// constructor here
// addItem here

public MenuItem[] getMenuItems() {


return menuItems;
}

public Iterator createIterator() {


return new DinerMenuIterator(menuItems);
}

// other menu methods here


}

15
public class Waitress {
PancakeHouseMenu pancakeHouseMenu;
DinerMenu dinerMenu;
public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu() {
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();
System.out.println(“MENU\n----\nBREAKFAST”);
printMenu(pancakeIterator);
System.out.println(“\nLUNCH”);
printMenu(dinerIterator);
}
private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem)iterator.next();
System.out.print(menuItem.getName() + “, “);
System.out.print(menuItem.getPrice() + “ -- “);
System.out.println(menuItem.getDescription());
} Fixing up the
}
// other methods here Waitress code
}
16
Testing our code
public class MenuTestDrive {
public static void main(String args[]) {
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinerMenu dinerMenu = new DinerMenu();

Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu);


waitress.printMenu();
}
}

17
What have we done so far?
Hard to Maintain New, Hip
Waitress Implementation Waitress Powered by Iterator
The Menus are not well encapsulated; we can The Menu implementations are now encapsulated.
see the Diner is using an Array and the The Waitress has no idea how the Menus hold
Pancake House an ArrayList. their collection of menu items.

We need two loops to iterate through the All we need is a loop that polymorphically handles any
MenuItems collection of items as long as it implements Iterator.

The Waitress is bound to concrete classes The Waitress now uses an interface (Iterator).
(MenuItem[] and ArrayList).

The Waitress is bound to two different The Menu interfaces are now exactly the same and, uh oh,
concrete Menu classes, despite their we still don’t have a common interface, which means the
interfaces being almost identical. Waitress is still bound to two concrete Menu classes.
We’d better fix that.

18
Bird’s eye view
Before we clean things up, let’s
get a bird’s eye view of our current
design.

19
Making some improvements...
Okay, we know the interfaces of PancakeHouseMenu
and DinerMenu are exactly the same and yet we haven’t
defined a common interface for them. So, we’re going to
do that and clean up the Waitress a little more.
First, let’s check out the java.util.Iterator
interface:
there are no
Dumb Questions
Q: What if I don’t want to provide the ability to remove Q: How does remove() behave under multiple threads that
something from the underlying collection of objects? may be using different iterators over the same collection of
objects?
A: The remove() method is considered optional. You don’t have
to provide remove functionality. But, obviously you do need to A: The behavior of the remove() is unspecified if the
provide the method because it’s part of the Iterator interface. If collection changes while you are iterating over it. So you
you’re not going to allow remove() in your iterator you’ll should be careful in designing your own multithreaded code
want to throw the runtime exception when accessing a collection concurrently.
java.lang.UnsupportedOperationException. The Iterator API
documentation specifies that this exception may be thrown from
remove() and any client that is a good citizen will check for
this exception when calling the remove() method.
20
Cleaning things up with java.util.Iterator
Let’s start with the PancakeHouseMenu, changing it over to
java.util.Iterator is going to be easy.
We just delete the PancakeHouseMenuIterator class, add an
import java.util.Iterator to the top of PancakeHouseMenu
and change one line of the PancakeHouseMenu:

import java.util.Iterator;

...

public Iterator createIterator() {


return menuItems.iterator();
}

21
import java.util.Iterator;
public class DinerMenuIterator implements Iterator {
MenuItem[] list;
int position = 0;

public DinerMenuIterator(MenuItem[] list) {


Now we need to make the
this.list = list; changes to allow the
}
public Object next() {
DinerMenu to work with
//implementation here java.util.Iterator.
}
public boolean hasNext() {
//implementation here
}
public void remove() {
if (position <= 0) {
throw new IllegalStateException
(“You can’t remove an item until you’ve done at least one next()”);
} if (list[position-1] != null) {
for (int i = position-1; i < (list.length-1); i++) {
list[i] = list[i+1];
}
list[list.length-1] = null;
}
}
}
22
We are almost there...
We just need to give the Menus a common interface and rework the Waitress a little.

public interface Menu {


public Iterator createIterator();
}

Now we need to add an implements Menu to both the


PancakeHouseMenu and the DinerMenu class definitions
and update the Waitress

23
import java.util.Iterator;
public class Waitress {
Menu pancakeHouseMenu;
Menu dinerMenu;
public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu() {
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();
System.out.println(“MENU\n----\nBREAKFAST”);
printMenu(pancakeIterator);
System.out.println(“\nLUNCH”);
printMenu(dinerIterator);
}
private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem)iterator.next();
System.out.print(menuItem.getName() + “, “);
System.out.print(menuItem.getPrice() + “ -- “);
System.out.println(menuItem.getDescription());
}
}
// other methods here
}
24
What does this get us?
The PancakeHouseMenu and DinerMenu classes implement an
interface, Menu. Waitress can refer to each menu object using the
interface rather than the concrete class. So, we’re reducing the
dependency between the Waitress and the concrete classes by
“programming to an interface, not an implementation.”

The new Menu interface has one method, createIterator(),


that is implemented by PancakeHouseMenu and DinerMenu.
Each menu class assumes the responsibility of creating a concrete
Iterator that is appropriate for its internal implementation of the
menu items.

25
Bird’s eye view

26
Iterator Pattern defined
▪ Once you have a uniform way of accessing the elements
of all your aggregate objects, you can write
polymorphic code that works with any of these
aggregates.
▪ The other important impact on your design is that the
Iterator Pattern takes the responsibility of traversing
elements and gives that responsibility to the iterator
object, not the aggregate object.
▪ This not only keeps the aggregate interface and
implementation simpler, it removes the responsibility
for iteration from the aggregate and keeps the
aggregate focused on the things it should be focused on
(managing a collection of objects), not on iteration.

27
Class Diagram

28
there are no
Dumb Questions
Q: Could I implement an Iterator that can go backwards as Q: If I’m using Java, won’t I always want to use the
well as forwards? java.util.Iterator interface so I can use my own
iterator implementations with classes that are already using
A: Definitely. In that case, you’d probably want to add two
the Java iterators?
methods, one to get to the previous element, and one to tell
you when you’re at the beginning of the collection of A: Probably. If you have a common Iterator interface, it will
elements. Java’s Collection Framework provides another certainly make it easier for you to mix and match your own
type of iterator interface called ListIterator. This aggregates with Java aggregates like ArrayList and Vector.
iterator adds previous() and a few other methods to the But remember, if you need to add functionality to your
standard Iterator interface. It is supported by any Iterator interface for your aggregates, you can always extend
Collection that implements the List interface. the Iterator interface.
Q: You said we can write “polymorphic code” using an Q: I’ve seen an Enumeration interface in Java; does that
iterator; can you explain that more? implement the Iterator Pattern?
A: When we write methods that take Iterators as A: We talked about this in the Adapter Pattern. Remember? The
parameters, we are using polymorphic iteration. That means java.util.Enumeration is an older implementation of
we are creating code that can iterate over any collection as Iterator that has since been replaced by
long as it supports Iterator. We don’t care about how the java.util.Iterator. Enumeration has two methods,
collection is implemented, we can still write code to iterate hasMoreElements(), corresponding to hasNext(), and
over it. nextElement(), corresponding to next().

29
Single Responsibility
What if we allowed our aggregates to implement their internal collections and related
operations AND the iteration methods? Well, we already know that would expand the number
of methods in the aggregate, but so what? Why is that so bad?

30
31
public class CafeMenu {
Hashtable menuItems = new Hashtable();
public CafeMenu() {
addItem(“Veggie Burger and Air Fries”,
“Veggie burger on a whole wheat bun, lettuce, tomato, and fries”,
true, 3.99);
addItem(“Soup of the day”,
“A cup of the soup of the day, with a side salad”,
false, 3.69);
addItem(“Burrito”,
“A large burrito, with whole pinto beans, salsa, guacamole”,
true, 4.29);
}
public void addItem(String name, String description,
boolean vegetarian, double price)
{
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.put(menuItem.getName(), menuItem);
}
public Hashtable getItems() {

}
return menuItems;
Taking a look at the
} Café Menu 32
Sharpen your pencil

Before passing this slide, quickly jot down the three things we have to do
to this code to fit it into our framework

1. implement the Menu interface


______________________________________________________

get rid of getItems()


2. ______________________________________________________

add createIterator() and return an Iterator that


3. ______________________________________________________
can step through the Hashtable values
______________________________________________________

33
Integrating the Cafe Menu into our framework is easy.
Reworking the Café Menu code Why? Because Hashtable is one of those Java
collections that supports Iterator.
But it’s not quite the same as ArrayList...
public class CafeMenu implements Menu {
Hashtable menuItems = new Hashtable();

public CafeMenu() { // constructor code here }

public void addItem(String name, String description,


boolean vegetarian, double price)
{
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.put(menuItem.getName(), menuItem);
}

public Hashtable getItems() {


return menuItems;
}

public Iterator createIterator() {


return menuItems.values().iterator();
}
} 34
Code Up Close
Hashtable is a little more complex than the ArrayList because
it supports both keys and values, but we can still get an
Iterator for the values (which are the MenuItems).

public Iterator createIterator() {


return menuItems.values().iterator();
}

35
public class Waitress {
Menu pancakeHouseMenu;
Menu dinerMenu;
Menu cafeMenu;
public Waitress(Menu pancakeHouseMenu, Menu dinerMenu, Menu cafeMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
this.cafeMenu = cafeMenu;
}
public void printMenu() {
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();
Iterator cafeIterator = cafeMenu.createIterator();
System.out.println(“MENU\n----\nBREAKFAST”);
printMenu(pancakeIterator);
System.out.println(“\nLUNCH”);
printMenu(dinerIterator);
System.out.println(“\nDINNER”);
printMenu(cafeIterator);
}
private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem)iterator.next();
System.out.print(menuItem.getName() + “, “);
System.out.print(menuItem.getPrice() + “ -- “);
System.out.println(menuItem.getDescription());
}

}
} Adding the Café Menu to the Waitress
36
public class MenuTestDrive {
public static void main(String args[]) {
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
DinerMenu dinerMenu = new DinerMenu();
CafeMenu cafeMenu = new CafeMenu();
Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu, cafeMenu);
waitress.printMenu();
}

Breakfast, lunch
AND dinner 37
What did we do?

38
We decoupled the Waitress....

39
. . . and we made the Waitress more extensible

40
But there’s more!

41
Iterators and Collections
Java Collections Framework
implements the
java.util.Collection interface

42
Code The Chefs have decided that they want to be able to alternate their lunch menu items; in other words,
they will offer some items on Monday, Wednesday, Friday and Sunday, and other items on Tuesday,
Magnets Thursday, and Saturday. Someone already wrote the code for a new “Alternating” DinerMenu Iterator
so that it alternates the menu items, but they scrambled it up and put it on the fridge in the Diner as a
joke. Can you put it back together? Some of the curly braces fell on the fl oor and they were too small
to pick up, so feel free to add as many of those as you need.

MenuItem menuItem = items[position]; if (position >= items.length || items[position] == null) {


position = position + 2; return false;
return menuItem; } else {
return true;
import java.util.Iterator; { }
import java.util.Calendar; }
implements Iterator
public class AlternatingDinerMenuIterator public void remove() {

throw new UnsupportedOperationException(


“Alternating Diner Menu Iterator does not support remove()”);
public Object next() {

MenuItem[] items; int position;


this.items = items;
Calendar rightNow = Calendar.getInstance(); {
position = rightNow.get(Calendar.DAY_OF_WEEK) % 2; } }

public boolean hasNext() {


public AlternatingDinerMenuIterator(MenuItem[] items)

43
import java.util.Iterator; Code Magnets
import java.util.Calendar;
public class AlternatingDinerMenuIterator implements Iterator {
Solutions
MenuItem menuItem = items[position];
position = position + 2;
return menuItem;
public AlternatingDinerMenuIterator(MenuItem[] items) {
this.items = items;
Calendar rightNow = Calendar.getInstance();
position = rightNow.get(Calendar.DAY_OF_WEEK) % 2; }
public boolean hasNext() {
if (position >= items.length || items[position] == null) {
return false;
} else {
return true;
} }

public Object next() { MenuItem[] items; int position; }


public void remove() {
throw new UnsupportedOperationException(
“Alternating Diner Menu Iterator does not support remove()”);
} 44
public void printMenu() {
Is the Waitress ready for prime time?
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();
Iterator cafeIterator = cafeMenu.createIterator();

System.out.println(“MENU\n----\nBREAKFAST”);
printMenu(pancakeIterator);

System.out.println(“\nLUNCH”);
printMenu(dinerIterator);

System.out.println(“\nDINNER”);
printMenu(cafeIterator);
}

It’s not the Waitress’ fault. We have done a great job of decoupling the menu implementation
and extracting the iteration into an iterator. But we still are handling the menus with
separate, independent objects – we need a way to manage them together.
45
public class Waitress {
ArrayList menus;
public Waitress(ArrayList menus) {
this.menus = menus;
}
public void printMenu() {
Iterator menuIterator = menus.iterator();
while(menuIterator.hasNext()) {
Menu menu = (Menu) menuIterator.next();
printMenu(menu.createIterator());
}
}
void printMenu(Iterator iterator) {
while (iterator.hasNext()) { Sounds like the chef is on to something.
MenuItem menuItem = (MenuItem) iterator.next();
System.out.print(menuItem.getName() + “, “); Let’s give it a try:
System.out.print(menuItem.getPrice() + “ -- “);
System.out.println(menuItem.getDescription());
}
} This looks pretty good, although we’ve lost the names of the
} menus, but we could add the names to each menu.

46
Just when we thought it was safe . . .
Now they want to add a dessert submenu.
▪ Now we have to support not only multiple menus,
but menus within menus.
▪ It would be nice if we could just make the dessert
menu an element of the DinerMenu collection.
▪ But that won’t work as it is now implemented.

47
What we want
(something like this):

We can’t assign a dessert menu to


a MenuItem array.

Time for a change!


48
What do we need?
We need to rework the chef’s implementation into something that is general
enough to work over all the menus (and now sub menus).

The reality is that we’ve reached a level of complexity such that if we don’t
rework the design now, we’re never going to have a design that can accommodate
further acquisitions or submenus.

So, what is it we really need out of our new design?


▪ We need some kind of a tree shaped structure that will accommodate menus,
submenus and menu items.
▪ We need to make sure we maintain a way to traverse the items in each menu
that is at least as convenient as what we are doing now with iterators.
▪ We may need to be able to traverse the items in a more flexible manner. For
instance, we might need to iterate over only the Diner’s dessert menu, or we
might need to iterate over the Diner’s entire menu, including the dessert
submenu. 49
50
The Composite Pattern defined
The problem of managing menus has taken on a new dimension
that Iterator doesn’t solve.

By putting menus and items in the same structure we create a


part-whole hierarchy; that is, a tree of objects that is made of
parts (menus and menu items) but that can be treated as a
whole, like one big über menu.

51
The Composite Pattern allows us to build
structures of objects in the form of trees that
contain both compositions of objects and
individual objects as nodes.

Using a composite structure, we can apply


the same operations over both
composites and individual
objects. In other words, in
most cases we can
ignore the differences
between compositions
of objects and individual objects.
52
Composite pattern
Class diagram
53
Designing Menus
with Composite
We need to create a
component interface; this acts as
the common interface for both
menus and menu items and allows
us to treat them uniformly.

In other words we can call the


same method on menus or menu
items.

Now, it may not make sense to


call some of the methods on a
menu item or a menu, but we can
deal with that, and we will in just
a moment.
54
public abstract class MenuComponent {
Implementing the Menu Component
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i) {
throw new UnsupportedOperationException();
}
public String getName() {
throw new UnsupportedOperationException();
} All components must implement
public String getDescription() { the MenuComponent interface;
throw new UnsupportedOperationException();
} however, because leaves and
public double getPrice() { nodes have different roles we
throw new UnsupportedOperationException();
} can’t always define a default
public boolean isVegetarian() { implementation for each
throw new UnsupportedOperationException();
} method that makes sense.
public void print() { Sometimes the best you can do
throw new UnsupportedOperationException();
} is throw a runtime exception.
} 55
public class MenuItem extends MenuComponent {
String name;
Implementing the Menu Item
String description;
boolean vegetarian;
double price;
public MenuItem(String name, String description, boolean vegetarian, double price) {
this.name = name; this.description = description;
this.vegetarian = vegetarian; this.price = price;
}

public String getName() { return name; }


public String getDescription() { return description; }
public double getPrice() { return price; }
public boolean isVegetarian() { return vegetarian; }

public void print() {


System.out.print(“ “ + getName());
if (isVegetarian()) {
System.out.print(“(v)”);
}
System.out.println(“, “ + getPrice()); this is the leaf class in the Composite
System.out.println(“ -- “ + getDescription()); diagram and it implements the behavior
}
}
of the elements of the composite.
56
public class Menu extends MenuComponent { Implementing the Composite Menu
ArrayList menuComponents = new ArrayList();
String name;
String description;
public Menu(String name, String description) {
this.name = name;
this.description = description;
}
public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
public void remove(MenuComponent menuComponent) {
menuComponents.remove(menuComponent);
}
public MenuComponent getChild(int i) {
return (MenuComponent)menuComponents.get(i);
}
public String getName() { return name; }
public String getDescription() { return description; }
public void print() {
System.out.print(“\n” + getName());
System.out.println(“, “ + getDescription());
System.out.println(“---------------------”);
}
} 57
Fixing the print() method

public class Menu extends MenuComponent {


ArrayList menuComponents = new ArrayList();
String name;
String description;
// constructor code here
// other methods here
public void print() {
System.out.print(“\n” + getName());
System.out.println(“, “ + getDescription());
System.out.println(“---------------------”);
Iterator iterator = menuComponents.iterator();
while (iterator.hasNext()) {
MenuComponent menuComponent = (MenuComponent)iterator.next();
menuComponent.print();
}
}
} 58
Getting ready for a test drive...
It’s about time we took this code for a test drive, but we need to update the
Waitress code before we do – after all she’s the main client of this code:

public class Waitress {


MenuComponent allMenus;

public Waitress(MenuComponent allMenus) {


this.allMenus = allMenus;
}

public void printMenu() {


allMenus.print();
}
}

59
One last thing before we write our test drive.
Let’s get an idea of what the menu composite is going to look like at runtime:

60
Okay, now we just need a test drive. Unlike our previous version, we’re going
Now for the test drive... to handle all the menu creation in the test drive. We could ask each chef to
give us his new menu, but let’s get it all tested first. Here’s the code:
public class MenuTestDrive {
public static void main(String args[]) {
MenuComponent pancakeHouseMenu = new Menu(“PANCAKE HOUSE MENU”, “Breakfast”);
MenuComponent dinerMenu = new Menu(“DINER MENU”, “Lunch”);
MenuComponent cafeMenu = new Menu(“CAFE MENU”, “Dinner”);
MenuComponent dessertMenu = new Menu(“DESSERT MENU”, “Dessert of course!”);
MenuComponent allMenus = new Menu(“ALL MENUS”, “All menus combined”);
allMenus.add(pancakeHouseMenu);
allMenus.add(dinerMenu);
allMenus.add(cafeMenu);
// add menu items here
dinerMenu.add(new MenuItem( “Pasta”,
“Spaghetti with Marinara Sauce, and a slice of sourdough bread”, true, 3.89));
dinerMenu.add(dessertMenu);
dessertMenu.add(new MenuItem( “Apple Pie”,
“Apple pie with a flakey crust, topped with vanilla icecream”, true, 1.59));
// add more menu items here
Waitress waitress = new Waitress(allMenus);
waitress.printMenu();
}
}
61
Test drive

We could say that the Composite Pattern takes


the Single Responsibility design principle and
trades it for transparency.
What’s transparency?

62
Flashback to Iterator
You know that we are already using Iterator in our internal Now we need to implement this method in the Menu and
implementation of the print() method, but we can also MenuItem classes:
allow the Waitress to iterate over an entire composite if
she needs to, for instance, if she wants to go through the
entire menu and pull out vegetarian items. public class Menu extends MenuComponent {
Iterator iterator = null;
// other code here doesn’t change
To implement a Composite iterator, let’s add a
createIterator() method in every component. We’ll public Iterator createIterator() {
start with the abstract MenuComponent class: if (iterator == null) {
iterator = new CompositeIterator(
menuComponents.iterator());
}
return iterator;
}
}

public class MenuItem extends MenuComponent {


// other code here doesn’t change
public Iterator createIterator() {
return new NullIterator();
}
} 63
import java.util.*;

The Composite Iterator


public class CompositeIterator implements Iterator {
Stack stack = new Stack();
public CompositeIterator(Iterator iterator) {
stack.push(iterator); The CompositeIterator is
}
public Object next() { a SERIOUS iterator. It’s got
if (hasNext()) { the job of iterating over the
Iterator iterator = (Iterator) stack.peek(); MenuItems in the component,
MenuComponent component = (MenuComponent) iterator.next();
if (component instanceof Menu) {
and of making sure all the child
stack.push(component.createIterator()); Menus (and child child Menus,
} and so on) are included.
return component;
} else {
return null; Here’s the code. Watch out,
} this isn’t a lot of code, but it
} can be a little mind bending.
public boolean hasNext() {
if (stack.empty()) { return false; }
else { Just repeat to yourself as you go through it
Iterator iterator = (Iterator) stack.peek(); “recursion is my friend, recursion is my friend.”
if (!iterator.hasNext()) {
stack.pop();
return hasNext();
} else {
return true;
}
}
}
public void remove() { throw new UnsupportedOperationException(); }
} 64
When we wrote the print() method in the MenuComponent class we used an
iterator to step through each item in the component and if that item was a Menu
(rather than a MenuItem), then we recursively called the print() method to handle
it. In other words, the MenuComponent handled the iteration itself, internally.

With this code we are implementing an external iterator so there is a lot more to keep
track of. For starters, an external iterator must maintain its position in the iteration
so that an outside client can drive the iteration by calling hasNext() and next().
But in this case, our code also needs to maintain that position over a composite,
recursive structure. That’s why we use stacks to maintain our position as we move
up and down the composite hierarchy.

65
The Null Iterator
Okay, now what is this Null Iterator all about?
Think about it this way: a MenuItem has nothing to iterate over,
right? So how do we handle the implementation of its
The second choice certainly seems better.
createIterator() method?
Let’s call it NullIterator and implement it.
Well, we have two choices:

Choice one: import java.util.Iterator;


public class NullIterator implements Iterator {
Return null
public Object next() {
We could return null from createIterator(), but then we’d return null;
need conditional code in the client to see if null was }
returned or not. public boolean hasNext() {
return false;
Choice two: }
public void remove() {
Return an iterator that always returns
throw new UnsupportedOperationException();
false when hasNext() is called }
This seems like a better plan. We can still return an iterator, but }
the client doesn’t have to worry about whether or not null is ever
returned. In effect, we’re creating an iterator that is a “no op”.
66
public class Waitress {
Give me the vegetarian menu
MenuComponent allMenus; Now we’ve got a way to iterate over every item of the Menu.
public Waitress(MenuComponent allMenus) { Let’s take that and give our Waitress a method that can tell
this.allMenus = allMenus; us exactly which items are vegetarian.
}
public void printMenu() {
allMenus.print();
}
public void printVegetarianMenu() {
Iterator iterator = allMenus.createIterator();
System.out.println(“\nVEGETARIAN MENU\n----”);
while (iterator.hasNext()) {
MenuComponent menuComponent = (MenuComponent)iterator.next();
try {
if (menuComponent.isVegetarian()) {
menuComponent.print();
}
} catch (UnsupportedOperationException e) {}
}
}
}
67
The magic of Iterator & Composite together...
Whooo! It’s been quite a
development effort to get our code
to this point.

Now we’ve got a general menu


structure that should last the
growing Diner empire for some time.

Now it’s time to sit back and order


up some veggie food:

68
▪ try/catch is meant for error handling,
not program logic.
What are our other options?

▪ We could have checked the runtime type of the


menu component with instanceof to make
sure it’s a MenuItem before making the call
to isVegetarian(). But in the process
LET’S TAKE A LOOK AT WHAT we’d lose transparency because we wouldn’t be
YOU’RE TALKING ABOUT treating Menus and MenuItems uniformly.
▪ We could also change isVegetarian() in
the Menus so that it returns false. This
try { provides a simple solution and we keep our
if (menuComponent.isVegetarian()) { transparency.
menuComponent.print();
} ▪ In our solution we are going for clarity: we really want to
} catch (UnsupportedOperationException e) {} communicate that this is an unsupported operation on the
Menu (which is different than saying isVegetarian() is
false). It also allows for someone to come along and actually
implement a reasonable isVegetarian() method for Menu
and have it work with the existing code.
69
PATTERN DESCRIPTION
Strategy Clients treat collections of objects and
individual objects Uniformly
Adapter Provides a way to traverse a collection of
objects without exposing the collection’s
Implementation
Iterator Simplifies the interface of a group of
classes
Facade Changes the interface of one or more
classes
Composite Allows a group of objects to be notified
when some state changes
Observer Encapsulates interchangeable behaviors and
uses delegation to decide which one to uses 70
Tools for your Design Toolbox
Bullet Points
▪ An Iterator allows access to an aggregate’s elements without
exposing its internal structure.
▪ An Iterator takes the job of iterating over an aggregate and
encapsulates it in another object.
▪ When using an Iterator, we relieve the aggregate of the
responsibility of supporting operations for traversing its data.
▪ An Iterator provides a common interface for traversing the items
of an aggregate, allowing you to use polymorphism when writing
code that makes use of the items of the aggregate.
▪ We should strive to assign only one responsibility to each class.
▪ The Composite Pattern provides a structure to hold both
individual objects and composites.
▪ The Composite Pattern allows clients to treat composites and
individual objects uniformly.
▪ A Component is any object in a Composite structure.
▪ Components may be other composites or leaf nodes.
▪ There are many design tradeoffs in implementing Composite. You
need to balance transparency and safety with your needs.
71

You might also like