第十一章 迭代器模式
1.1 一般的菜单遍历
餐厅都有自己的一份菜单,而菜单由菜单项组成,每个菜单项描述菜名、菜价和描述等信息。当需要遍历时,一般会想到如下遍历的方式:
首先定义一个菜单项 MenuItem:
package headfirst.designpatterns.iterator.generalmenu;
public class MenuItem{
String name;
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 boolean isVegetarian() {
return vegetarian;
}
public double getPrice() {
return price;
}
}
然后定义一个煎饼屋菜单 PancakeHouseMenu,使用 ArrayList 存储菜单项。
package headfirst.designpatterns.iterator.generalmenu;
import java.util.ArrayList;
public class PancakeHouseMenu implements Menu{
ArrayList<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, and blueberry syrup",
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;
}
}
同时,定义一份DinerMenu,使用数组存储菜单项。
package headfirst.designpatterns.iterator.generalmenu;
public class DinerMenu implements Menu{
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 saurkraut, relish, onions, topped with cheese",
false, 3.05);
addItem("Steamed Veggies and Brown Rice",
"Steamed vegetables over brown rice", true, 3.99);
addItem("Pasta",
"Spaghetti with Marinara Sauce, and a slice of sourdough bread",
true, 3.89);
}
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;
}
}
在客户代码中,遍历两份菜单的菜单项:
package headfirst.designpatterns.iterator.generalmenu;
import java.util.List;
public class Waitress {
Menu pancakeHouseMenu;
Menu dinerMenu;
public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu(){
List<MenuItem> breakfastItems = ((PancakeHouseMenu) pancakeHouseMenu).getMenuItems();
// 在对集合进行遍历的时候,breakfastItems.size() 、breakfastItems.get(i) 是变化的,可能因breakfastItems中采用存储菜单项的集合变化而变化,因此需要封装。
for (int i = 0; i < breakfastItems.size(); i++) {
MenuItem menuItem = breakfastItems.get(i);
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
System.out.println("-------------------------------");
MenuItem[] lunchItems = ((DinerMenu) dinerMenu).getMenuItems();
for (int i = 0; i < lunchItems.length; i++) {
MenuItem menuItem = lunchItems[i];
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}
}
在对集合进行遍历的时候,breakfastItems.size() 、breakfastItems.get(i) 是变化的,可能因breakfastItems中采用存储菜单项的集合变化而变化,因此需要对代码中变化的部分进行封装。由此,引入迭代器模式。
1.2 使用迭代器模式进行菜单遍历
我们再明确一下菜单与菜单项的关系,菜单负责管理了诸多个菜单项,把菜单项对象堆起来成为一个集合。
为了使用迭代器模式来遍历菜单中的菜单项,我们需要为每个菜单创建一个迭代器对象,以用来遍历。
因此,设计的类图如下图所示。
-
Waitress 是客户代码;
-
客户代码需要遍历菜单项,因此依赖PancakeHouseMenu 和 DinerMenu两个菜单对象;
-
客户代码是通过Iterator来遍历菜单项对象的;
-
每个菜单对象通过创建具体的菜单迭代器对象进行遍历。
定义迭代器接口Iterator:
package headfirst.designpatterns.iterator.iteratormenu;
public interface Iterator {
boolean hasNext();
Object next();
}
定义了菜单接口,每个菜单都应该具有创建对应迭代器的能力:
package headfirst.designpatterns.iterator.iteratormenu;
public interface Menu {
Iterator createIterator();
}
定义菜单 DinerMenu 实现了 Menu 接口,包含了createIterator()方法,以创建迭代器对象:
package headfirst.designpatterns.iterator.iteratormenu;
public class DinerMenu implements Menu {
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 saurkraut, relish, onions, topped with cheese",
false, 3.05);
addItem("Steamed Veggies and Brown Rice",
"Steamed vegetables over brown rice", true, 3.99);
addItem("Pasta",
"Spaghetti with Marinara Sauce, and a slice of sourdough bread",
true, 3.89);
}
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;
}
}
@Override
public Iterator createIterator() {
Iterator dinerMenuIterator = new DinerMenuIterator(menuItems);
return dinerMenuIterator;
}
}
定义了菜单 PancakeHouseMenu 实现了 Menu 接口,包含了createIterator()方法,以创建迭代器对象:
package headfirst.designpatterns.iterator.iteratormenu;
import java.util.ArrayList;
import java.util.List;
public class PancakeHouseMenu implements Menu {
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, and blueberry syrup",
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);
}
@Override
public Iterator createIterator() {
Iterator pancakeHouseMenuIterator = new PancakeHouseMenuIterator(menuItems);
return pancakeHouseMenuIterator;
}
}
再定义具体的菜单迭代器对象 DinerMenuIterator 和 PancakeHouseMenuIterator:
package headfirst.designpatterns.iterator.iteratormenu;
public class DinerMenuIterator implements Iterator {
MenuItem[] items;
int position =0;
public DinerMenuIterator(MenuItem[] items) {
this.items = items;
}
@Override
public boolean hasNext() {
if (position >=items.length || items[position] == null){
return false;
}else{
return true;
}
}
@Override
public Object next() {
MenuItem menuItem = items[position];
position = position+1;
return menuItem;
}
}
package headfirst.designpatterns.iterator.iteratormenu;
import java.util.List;
public class PancakeHouseMenuIterator implements Iterator{
List<MenuItem> items;
public PancakeHouseMenuIterator(List<MenuItem> items) {
this.items = items;
}
int position =0;
@Override
public boolean hasNext() {
return items.size() > position;
}
@Override
public Object next() {
return items.get(position++);
}
}
使用客户代码进行测试:
package headfirst.designpatterns.iterator.iteratormenu;
public class Waitress {
Menu pancakeHouseMenu;
Menu dinerMenu;
public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu(){
Iterator dinerMenuIterator = dinerMenu.createIterator();
// 即使菜单组织菜单项使用的数据结构变化,例如List -> Map,在维护时,这部分代码也不会变化。
while (dinerMenuIterator.hasNext()){
MenuItem menuItem = (MenuItem)dinerMenuIterator.next();
printMenuItem(menuItem);
}
System.out.println("------------------------");
Iterator pancakeHouseMenuIterator = pancakeHouseMenu.createIterator();
while (pancakeHouseMenuIterator.hasNext()){
MenuItem menuItem = (MenuItem)pancakeHouseMenuIterator.next();
printMenuItem(menuItem);
}
}
public void printMenuItem(MenuItem menuItem){
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}
输出结果:
Vegetarian BLT, 2.99 -- (Fakin') Bacon with lettuce & tomato on whole wheat
BLT, 2.99 -- Bacon with lettuce & tomato on whole wheat
Soup of the day, 3.29 -- Soup of the day, with a side of potato salad
Hotdog, 3.05 -- A hot dog, with saurkraut, relish, onions, topped with cheese
Steamed Veggies and Brown Rice, 3.99 -- Steamed vegetables over brown rice
Pasta, 3.89 -- Spaghetti with Marinara Sauce, and a slice of sourdough bread
------------------------
K&B's Pancake Breakfast, 2.99 -- Pancakes with scrambled eggs, and toast
Regular Pancake Breakfast, 2.99 -- Pancakes with fried eggs, sausage
Blueberry Pancakes, 3.49 -- Pancakes made with fresh blueberries, and blueberry syrup
Waffles, 3.59 -- Waffles, with your choice of blueberries or strawberries
当然,我们在这里使用了自定义的 Iterator 接口,也可以使用 java.util.Iterator 包下的接口。
1.4 总结
迭代器模式:提供了一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
迭代器模式的类图如下:
参考
[1] Freeman E. Head First 设计模式[M] 中国电力出版社.
[2] 菜鸟教程.