《Head First Design Patterns》:第9章,迭代器(Iterator)模式

迭代器模式的定义

迭代器模式提供了一种方法来顺序地访问一个聚合对象中的各个元素,而又不暴露其底层的表现形式。

客户直接面向AggregateIterator进行编程,每个具体的聚合类持有一个对象的集合,并实现一个方法返回对其内部对象集合的迭代器。

场景

餐厅和煎饼屋合并

场景描述

餐厅和煎饼屋合并了,煎饼屋负责提供早餐,餐厅负责提供午餐。已知:a)煎饼屋和餐厅都有各自的菜单,菜单上有多个菜单项,菜单项是统一的(包含名称、描述、价格);b)但煎饼屋和餐厅的菜单存储菜单项的方式是不同的:煎饼屋使用的是ArrayList,餐厅使用的是数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class PancakeHouseMenu {
List<MenuItem> menuItems;

public PancakeHouseMenu() {
menuItems = new ArrayList<MenuItem>();
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<MenuItem> getMenuItems() {
return menuItems;
}

// other menu methods here
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class DinerMenu {
static final int MAX_ITEMS = 6;
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 sauerkraut, 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
}

既然已经合并了,需要统一所提供的菜单规格,否则如果女招待员想要进行打印菜单printMenu()之类的操作就会变得比较繁琐,需要如下步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
ArrayList<MenuItem> breakfastItems = pancakeHouseMenu.getMenuItems();

DinerMenu dinerMenu = new DinerMenu();
MenuItem[] lunchItems = dinerMenu.getMenuItems();


// 打印早餐
for (int i = 0; i < breakfastItems.size(); i++) {
MenuItem menuItem = breakfastItems.get(i);
System.out.print(menuItem.getName() + " ");
System.out.println(menuItem.getPrice() + " ");
System.out.println(menuItem.getDescription());
}

// 打印午餐
for (int i = 0; i < lunchItems.length; i++) {
MenuItem menuItem = lunchItems[i];
System.out.print(menuItem.getName() + " ");
System.out.println(menuItem.getPrice() + " ");
System.out.println(menuItem.getDescription());
}

以上代码,总是需要处理两种菜单,用两个循环遍历这些菜单项。这只是实现printMenu()一个功能,可以想象其它类似方法的实现同样要进行两个这样的循环遍历。

方案

上例中变化的部分是由不同集合类型所造成的遍历。能否将遍历封装起来?

使用迭代器可以实现对各种对象集合进行迭代。在PancakeHouseMenu类可以实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public interface Iterator {
boolean hasNext();
MenuItem next();
}

public class DinerMenuIterator implements Iterator {
MenuItem[] items;
int position = 0;

public DinerMenuIterator(MenuItem[] items) {
this.items = items;
}

public MenuItem 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;
}
}
}

public class DinerMenu {
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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 = iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}

// other methods here
}

方案的改良

我们已经知道这两份菜单的接口完全一样,但没有为它们设计一个共同的接口,为了让女招待员的工作变得更加干净,我们下面抽象出了Menu接口。同时我们会尝试直接使用Java中的Iterator接口。这样,煎饼屋菜单和餐厅菜单都实现了Menu接口,女招待员可以利用接口(而不是具体类)引用每一个菜单对象。