0%

八、迭代器与组合模式

引言

对象村餐厅和对象村煎饼屋合并了,但是它们的菜单存储方式不同,一个是用Arraylist,另一个是数组实现的。

  • 菜单项-MenuItem
1
2
3
4
5
6
7
8
9
10
11
@Data
@AllArgsConstructor
public class MenuItem {
private String name;
private String description;
/**
* 是否为素食
*/
private boolean vegetarian;
private double price;
}
  • 煎饼屋菜单-ArrayList实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
public class PancakeHouseMenu {
private ArrayList<MenuItem> menuItems;
public PancakeHouseMenu() {
menuItems = new ArrayList<>();
addItem("K&B薄煎饼早餐", "薄煎饼,鸡蛋和吐司", true, 2.99);
addItem("薄煎饼早餐", "薄煎饼带鸡蛋、香肠", false, 2.99);
addItem("蓝莓薄煎饼", "蓝莓薄煎饼", true, 3.49);
addItem("松饼", "松饼,可以选择蓝莓或者草莓", 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);
}
}
  • 餐厅菜单 - 数组实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DinerMenu {
private static final int MAX_ITMES = 6;
private int numberOfItems = 0;
@Getter
MenuItem[] menuItems;
public DinerMenu() {
this.menuItems = new MenuItem[MAX_ITMES];
addItem("素食BIT", "素食BIT", true, 2.99);
addItem("BIT", "培根,生菜和西红柿", false, 2.99);
addItem("例汤", "例汤", false, 3.29);
addItem("热狗", "热狗,酸菜莓", false, 3.59);
}
public void addItem(String name, String description, boolean vegetarian, double price){
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
if (numberOfItems >= MAX_ITMES){
System.out.println("餐厅菜单满了,放不下了!");
}else {
menuItems[numberOfItems] = menuItem;
numberOfItems++;
}
}
}

两种不同的菜单会带来什么问题

让我们试着实现一个同时使用这2个菜单的客户代码。即创建一个java版本的女招待,按如下规格实现。
68640047

我们总是需要处理2个菜单,并且用2个循环去遍历这些项。如果还有第三家餐厅以不同的形式出现,我们就需要3个循环。
目前煎饼屋和餐厅都不想改变他们的代码,因为这些代码使用的地方太多了。如果我们能找到一种方法,让他们的菜单实现一个相同的接口。这样,我们可以最小化女招待代码中的具体引用,同时还有希望摆脱这2个菜单所需的多个循环。

会见迭代器模式

关于迭代器模式,你所需要知道的第一件事情,就是它依赖于一个称为迭代器的接口。

1
2
3
4
public interface Iterator<T> {
boolean hasNext();
T next();
}

现在,我们实现一个具体的迭代器,为餐厅菜单服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DinerMenuIterator implements Iterator<MenuItem> {
MenuItem[] menuItems;
int position = 0;
public DinerMenuIterator(MenuItem[] menuItems) {
this.menuItems = menuItems;
}
@Override
public boolean hasNext() {
if (position >= menuItems.length || menuItems[position] == null){
return false;
}else {
return true;
}
}
@Override
public MenuItem next() {
MenuItem menuItem = menuItems[position];
position++;
return menuItem;
}
}

改进

首先,java util下已经有了Iterator接口,我们不需要重复创建。其次ArrayList已经有了获取迭代器的方法,我们不需要自己在定义了一个针对于餐厅菜单的迭代器。女招待只需要知道有2种菜单,然后可以使用迭代器来统一遍历菜单。

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
public class Waitress {
Menu pancakeHouseMenu;
Menu dinerMenu;
/**
* 构造女招待的时候,需要传入2个菜单
* @param pancakeHouseMenu
* @param dinerMenu
*/
public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}
public void printMenu(){
Iterator<MenuItem> pancakeHouseMenuIterator = pancakeHouseMenu.getIterator();
Iterator<MenuItem> dinerMenuIterator = dinerMenu.getIterator();
System.out.println("煎饼屋菜单");
printMenu(pancakeHouseMenuIterator);
System.out.println("餐厅菜单");
printMenu(dinerMenuIterator);
}
private void printMenu(Iterator<MenuItem> iterator){
while (iterator.hasNext()){
MenuItem item = iterator.next();
System.out.println(item);
}
System.out.println("----------");
}
}

类图:
74970366

定义迭代器

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

在设计中,使用迭代器的影响是很明显的:如果你有一个统一的方法访问聚合中的每一个对象,你就可以编写多态的代码合这些聚合搭配使用。如同前面的printMenu()方法一样,只要有了迭代器这个方法,根本不用管菜单项是由数据还是ArrayList实现的。

另外,迭代器模式把元素之间游走的责任交给了迭代器,而不是聚合对象。这不仅让聚合的接口变得更简洁,也可以让聚合更专注它所专注的事情上面。

  • 设计原则

一个类应该只有一个引起变化的原因。

类的每个责任都有改变的潜在区域。超过一个责任,意味着超过一个改变的区域。
这个原则告诉我们,尽量让每个类保持单一责任。
目前,每次我们有新的菜单项加入,就需要修改女招待的代码。这违反了开闭原则。因此,我们可以使用一个ArrayList来存放菜单项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

@Data
public class Waitress {
private List<Menu> menus = new ArrayList<>();
public void printMenu(){
for (Menu menu : menus) {
Iterator<MenuItem> iterator = menu.getIterator();
printMenu(iterator);
}
}
private void printMenu(Iterator<MenuItem> iterator){
while (iterator.hasNext()){
MenuItem item = iterator.next();
System.out.println(item);
}
System.out.println("----------");
}
}

组合模式

现在,我们期望餐厅菜单中能有一份甜点子菜单。
76483900

组合模式允许你将对象组合成树形结构来表现“整体部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合
以菜单为例:组合模式能够创建一个树形结构,在同一个结构中处理嵌套菜单和菜单项组。通过将菜单和项放在相同的结构中,我们创建了“整体、部分”层次的结构。
组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象。
使用组合结构,我们能把相同的操作应用到组合和个别对象上。换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。
77606045

利用组合模式设计菜单

我们需要创建一个组件接口来作为菜单和菜单项的共同接口,让我们能够用统一的做法来处理菜单和菜单项。
78101926

源码:https://github.com/chentianming11/design-pattern
iterator包!