假设我们的早餐要在煎饼屋吃,而午餐要在餐厅吃,这是两家不同的餐厅,每一家餐厅的菜单都有各自不同的数据结构实现(但是有一点相同的地方,就是具体的产品上用的同一个数据结构),那么我们该如何设计一个程序来实现这个吃饭的功能呢?
首先我们先看一个煎饼屋的类
可以看到类主要使用了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
36
37
38
39public class PancakeHouseMenu {
ArrayList menuItems;
public PancakeHouseMenu() {
menuItems = new ArrayList();
addItem("K&R C pancake",
"This is C Bible",
true,
1.99);
addItem("Bjarne C++ pancake",
"This is a magic C++ language",
false,
2.99);
addItem("Guido Python pancake",
"This is a simple and useful language",
true,
3.99);
addItem("Gosling java pancake",
"Nothing to say!",
false,
4.99);
}
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;
}
}
然后我们看下午餐餐厅的类
这个类中主要使用了数组作为数据结构
1 |
|
它们为什么不用相同的数据结构呢?可能是不同的人开发的吧,这个我们不能强求啊。不过好歹它们的每个具体项目还是用到的相同的数据结构(其实这里不怕,哪怕它们用的不是相同的结构,我们也可以用适配器模式来解决)。
1 | public class MenuItem { |
现在有了菜单的类,我们就需要一个服务员的类来给我们提供相应的餐食服务了,如下所示:
因为早餐和午餐使用了不同的数据结构,所以服务员要分别处理这两种数据结构,进而提供相应的遍历访问服务。
1 | public class Waitress { |
具体代码归档位置如下:
https://github.com/changyuanchn/DesignPattern/tree/main/src/com/changyuan/IteratorPattern/baseDemo
那么现在问题来了,假设我们需要添加更多的类型,那么Waitress就要适配更多的类型的修改(printMenu函数中),而且这种模式还向Waitress暴露了太多的菜单类的细节(比如一个是ArrayList实现的,一个是数组实现的,在printMenu函数中暴露的一目了然)。那么我们即不想暴露太多细节,又不想让Waitress适配太多,该怎么做呢?
上面的Waitress代码中,如果我们在printMenu函数中能够用一种方式进行遍历就好了。迭代器模式就可以解决这个问题。
迭代器模式提供了一种方法顺序的访问一个聚合对象中的各个元素,而又不暴露其内部实现
迭代器实际上就是封装了上面的遍历,也就是说它依赖于一个名为迭代器的接口。对于每一个数据结构,都有一个迭代器的接口实现。
下面我们通过代码来具体看下:
首先我们需要定义迭代器的接口:
接口主要有两个函数,一个是判断是否有下一个节点,另一个是访问下一个节点1
2
3
4public interface Iterator {
boolean hasNext();
Object next();
}
然后我们针对每一种餐厅的类型,创建相应的迭代器:
这个迭代器主要实现上面的两个接口
1 | public class PancakeHouseMenuIterator implements Iterator { |
1 | public class DinnerMenuIterator implements Iterator{ |
然后要修改我么你的餐厅类,之前的实现中,我们直接获取了整个的menuItems,在下面的这个函数中,实际上也暴露了程序内部的实现的细节(比如说我们用数组MenuItem[]来实现的菜单项管理)。
1 | public MenuItem[] getMenuItems() { |
现在我们有了迭代器了,我们就不需要这个函数了,我们直接返回迭代器就好了。针对不同的类返回其匹配的迭代器
1 | public class PancakeHouseMenu { |
1 | public class DinnerMenu { |
然后我们在Waitress类中就可以用Iterator来实现对菜单项目的访问了,无论对于PancakeHouseMenu还是DinnerMenu,都可以统一用Iterator来遍历了,就不用在printMenu函数中写两套逻辑了。
1 |
|
具体的代码归档路径如下所示
当前的重构实现已经解决了我们之前提出的问题,那么上面的代码是不是就没有问题了呢?我们发现我们的Waitress是基于具体实现在编程,而不是接口,这违背了我们之前的设计原则
针对接口编程,而不是针对实现编程
同时我们也使用java库自带的迭代器来重构下这个需求。
首先我们创建一个menu的接口
1 | public interface Menu { |
我们的餐厅类都要实现这个接口,这样在Waitress中就可以不针对具体的实现了。
而由于我们的ArrayList本身就实现了Iterator接口,所以这里就可以直接使用,而不用对PancakeHouseMenu新创建Iterator接口了。
1 | public class PancakeHouseMenu implements Menu { |
1 | public class DinnerMenu implements Menu { |
这样我们的waitress类就可以做如下的修改了
1 | public class Waitress { |
通过上面的重构,当我们需要添加新的餐厅类型,也会变得比较简单了。
具体的代码请参见下面连接,在代码中我们新添加了CafeMenu类
下面就是上面的demo的类图
设计原则:
一个类应该只有一个引起变化的原因
内聚
用来度量一个类或模块紧密地达到单一目的或责任。当一个模块或者类被设计成只支持一组相关的功能时,我们说它具有高内聚