设计模式之迭代器模式

设计模式之迭代器模式

管理良好的集合

有许多种方法可以把对象堆起来成为一个集合。你可以把它们放进数组、堆栈、列表或者是散列表中。接下来我们会学习如何让客户遍历你的对象而又无法窥视你存储对象的方式;也将学习如何创建一些对象超集合,能够一口气就跳过某些让人望而生畏的数据结构。

餐厅和煎饼屋合并了

现在我们可以去同一地方就可以享用煎饼早餐,和午餐了。但是,好像有一点儿麻烦。。。因为煎饼屋使用的ArrayList,而餐厅使用的是数组,它们都不想改变实现,毕竟有太多的代码依赖于它们了。

查看菜单项

让我们检查每份菜单上的项目和实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 菜单项
* @author Mr.zxb
* @date 2020-08-30 21:13:06
*/
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;
}
// 省略 get/set方法
}

煎饼屋菜单实现

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
/**
* 煎饼屋的菜单
* @author Mr.zxb
* @date 2020-08-30 21:15:37
*/
public class PancakeHouseMenu {
/**
* 这里使用ArrayList 实现
*/
private final ArrayList<MenuItem> menuItems;

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

addItem("煎饼早餐", "炒鸡蛋煎饼,加面包", true, 2.99);
addItem("常规煎饼早餐", "煎饼配煎蛋,香肠", false, 2.99);
addItem("蓝莓煎饼", "用新鲜蓝莓制成的煎饼", true, 3.49);
addItem("华夫饼", "华夫饼,您可以选择蓝莓或草莓", true, 3.59);
}

/**
* 新增菜单
* @param name
* @param description
* @param vegetarian
* @param price
*/
public void addItem(String name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.add(menuItem);
}

/**
* 返回菜单列表
* @return
*/
public ArrayList<MenuItem> getMenuItems() {
return menuItems;
}
}

餐厅菜单实现

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
/**
* 晚餐菜单
* @author Mr.zxb
* @date 2020-08-30 21:26:39
*/
public class DinerMenu {
public static final int MAX_ITEMS = 4;
private int numberOfItems = 0;
private final MenuItem[] menuItems;

public DinerMenu() {
menuItems = new MenuItem[MAX_ITEMS];

addItem("Vegetarian BLT", "全麦培根莴苣和番茄", true, 2.99);
addItem("BLT", "培根配生菜和番茄全麦", false, 2.99);
addItem("当天的汤", "当天的汤,配以土豆沙拉", false, 3.29);
addItem("热狗", "热狗, 配酸菜,调味,洋葱,加奶酪", false, 3.99);
}

/**
* 新增菜单
* @param name
* @param description
* @param vegetarian
* @param price
*/
public void addItem(String name, String description, boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
if (numberOfItems >= MAX_ITEMS) {
System.out.println("Sorry, menu is full! Can't add item to menu");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}

/**
* 返回菜单列表
* @return
*/
public MenuItem[] getMenuItems() {
return menuItems;
}
}

服务员打印菜单

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
public class Waitress {
private final PancakeHouseMenu pancakeHouseMenu;
private final DinerMenu dinerMenu;

public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}

// 打印合并后的餐厅菜单项
// 我们总是要处理两个菜单,并且用两个循环遍历这些项。如果还有第三家餐厅以不同的实现,我们就需要有三个循环。。
public void printMenu() {
ArrayList<MenuItem> pancakeHouseMenuMenuItems = pancakeHouseMenu.getMenuItems();
for (int i = 0; i < pancakeHouseMenuMenuItems.size(); i++) {
MenuItem menuItem = pancakeHouseMenuMenuItems.get(i);
System.out.print(menuItem.getName() + " ");
System.out.println(menuItem.getPrice() + " ");
System.out.println(menuItem.getDescription());
}

MenuItem[] dinerMenuMenuItems = dinerMenu.getMenuItems();
for (int i = 0; i < dinerMenuMenuItems.length; i++) {
MenuItem menuItem = dinerMenuMenuItems[i];
System.out.print(menuItem.getName() + " ");
System.out.println(menuItem.getPrice() + " ");
System.out.println(menuItem.getDescription());
}
}
}

可以封装遍历吗?

我们可以封装变化的部分。很明显,在这里发生变化的是:由不同的集合类型所造成的遍历。

在餐厅加入一个迭代器

想要在餐厅中加入一个迭代器,我们需要先定义迭代器接口:

1
2
3
4
5
6
7
8
9
/**
* 迭代器接口
* @author Mr.zxb
* @date 2020-08-30 21:45:06
*/
public interface Iterator {
boolean hasNext();
Object next();
}

实现具体的迭代器,为餐厅菜单服务:

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
public class DinerMenuIterator implements Iterator {

private final 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;
}
return true;
}

@Override
public Object next() {
MenuItem item = items[position];
position++;
return item;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class PancakeHouseIterator implements Iterator {
private final ArrayList<MenuItem> menuItems;
int position = 0;

public PancakeHouseIterator(ArrayList<MenuItem> menuItems) {
this.menuItems = menuItems;
}

@Override
public boolean hasNext() {
if (position >= menuItems.size() || menuItems.get(position) == null) {
return false;
}
return true;
}

@Override
public Object next() {
MenuItem menuItem = menuItems.get(position);
position++;
return menuItem;
}
}

用迭代器改写餐厅菜单

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
/**
* 晚餐菜单
* @author Mr.zxb
* @date 2020-08-30 21:26:39
*/
public class DinerMenu {
public static final int MAX_ITEMS = 4;
private int numberOfItems = 0;
private final MenuItem[] menuItems;

// 省略其他方法...

/**
* 返回菜单列表
* @return
*/
public MenuItem[] getMenuItems() {
return menuItems;
}

/**
* 返回一个迭代器的实现
* @return
*/
public Iterator createIterator() {
return new DinerMenuIterator(getMenuItems());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class PancakeHouseMenu {
/**
* 这里使用ArrayList 实现
*/
private final ArrayList<MenuItem> menuItems;

// 省略其他方法...

/**
* 返回菜单列表
* @return
*/
public ArrayList<MenuItem> getMenuItems() {
return menuItems;
}

private Iterator createIterator() {
return new PancakeHouseIterator(getMenuItems());
}
}

修正服务员打印菜单

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
public class Waitress {

private final PancakeHouseMenu pancakeHouseMenu;
private final DinerMenu dinerMenu;

public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}

public void printMenu() {
Iterator iterator = pancakeHouseMenu.createIterator();
printMenu(iterator);
Iterator dinerMenuIterator = dinerMenu.createIterator();
printMenu(dinerMenuIterator);
}

public void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem) iterator.next();
System.out.print(menuItem.getName() + " ");
System.out.println(menuItem.getPrice() + " --");
System.out.println(menuItem.getDescription());
}
}
}

测试我们的代码

1
2
3
4
5
6
7
8
9
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();
}
}

使用Java的Iterator接口改良菜单

1
2
3
4
public interface Menu {
// 简单接口,让客户能够取得一个菜单项迭代器
Iterator createIterator();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 煎饼屋的菜单
* @author Mr.zxb
* @date 2020-08-30 21:15:37
*/
public class PancakeHouseMenu implements Menu {
/**
* 这里使用ArrayList 实现
*/
private final ArrayList<MenuItem> menuItems;

// 省略其他方法...

@Override
public Iterator createIterator() {
return menuItems.iterator();
}
}
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
import java.util.Iterator;
public class DinerMenuIterator implements Iterator {

private final 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;
}
return true;
}

@Override
public Object next() {
MenuItem item = items[position];
position++;
return item;
}

@Override
public void remove() {
if (position <= 0) {
throw new IllegalStateException("You can't remove an item until you've done at least one next()");
}
if (items[position - 1] != null) {
for (int i = position - 1; i < (items.length - 1); i++) {
items[i] = items[i + 1];
}
items[items.length - 1] = null;
}
}
}

改造我们的服务员类:

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
public class Waitress {

private final Menu pancakeHouseMenu;
private final Menu dinerMenu;

public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}

public void printMenu() {
Iterator iterator = pancakeHouseMenu.createIterator();
printMenu(iterator);
Iterator dinerMenuIterator = dinerMenu.createIterator();
printMenu(dinerMenuIterator);
}

private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem) iterator.next();
System.out.print(menuItem.getName() + " ");
System.out.println(menuItem.getPrice() + " ");
System.out.println(menuItem.getDescription());
}
}
}

改良的好处

煎饼屋菜单和餐厅菜单的类,都实现了Menu接口,服务员类可以利用接口(而不是具体类)引用每一个菜单对象。这样,通过“针对接口编程,而不是针对实现编程”,这样就可以减少服务员类与具体类之间的依赖。

定义迭代器模式

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

总结

  • 迭代器允许访问聚合的元素,而不需要暴露它的内部结构
  • 迭代器将遍历聚合的工作封装进一个对象中
  • 当使用迭代器的时候,我们依赖聚合提供遍历
  • 迭代器提供了一个通用的接口,让我们遍历聚合项,当我们编码使用聚合的项时,就可以使用多态机制
  • 我们应该努力让一个类只分配一个责任