20.想走?可以!先买票 - 迭代器模式(Iterator Pattern)

20.1 乘车买票,不管你是谁?

  • 时间: 5月26日10点   地点: 一公交车上   人物:大鸟,小菜

     这天是周末, 小菜和大鸟一早出门玩儿, 上了公交, 车内很挤.

     “上车的乘客请买票.” 售票员一边在人缝中穿插着, 一边说道.
     ……

20.2 迭代器模式

     “小菜, 今天你真的见到强人了吧.” 大鸟下车后, 对小菜说道.
     “这个售票员, 是在够强,” 小菜学者模仿到, “想走,可以.先买票再说.”
     “这售票员其实在做一件很重要的事情, 就是把车厢内所有的人都遍历一遍, 不放过每一个买票的乘客. 这也是一个设计模式的体现.”
     “大鸟, 你也够强, 什么都可以往设计模式上套, 这也是模式?”
     “当然是模式. 这个模式就叫迭代器模式.”

迭代器模式(Iterator), 提供一种顺序访问一个聚合对象中各个元素, 而又不暴露该对象的内部表示.[DP]

     “你想呀, 售票员才不过你上来的是物(行李), 不管是中国人还是外国人, 不管是不是内部员工, 甚至哪怕是马上要被抓走的小偷, 只要是来乘车的乘客, 就必须买票. 同样道理, 当你需要访问一个聚集对象, 而且不管这些对象是什么都需要遍历的时候, 你就应该考虑用迭代器模式. 另外, 售票员从车头到车尾来售票, 也可以从车尾到车头来售票, 也就是说, 你需要对聚集有多种遍历时, 可以考虑使用迭代器模式. 由于不管乘客干了什么, 售票员的做法始终是相同的, 都是从第一个开始, 下一个是谁, 是否结束, 当前售到那个人了, 这些方法每天他都在做, 也就是说, 为了遍历不同的聚集结构提供如, 开始, 下一个, 是否结束, 当前项是哪一个的接口.
     “听你这么一说, 好像这个模式也不简单”
     “哈, 本来这个模式还有点意思, 不过现今看来, 迭代器模式的实用价值远不如学习价值大了, MartinFlower 甚至在自己的网站上提出撤销此模式. 因为现在高级编程语言如 C#, Java, 等本身已经把这个模式做在语言中了.”
     “哦, 是什么?”
     “哈, foreach 你熟悉吧?”
     “啊, 原来是它, 没错没错, 他就是不需要知道集合对象是什么, 就可以遍历所有的对象的循环工具, 非常好用.”
     另外还有 Iterable 接口也是为了迭代器模式而准备的. 不管如何, 学习一下 GoF 的迭代器模式的基本结构, 还是很有学习价值的.

20.3 迭代器模式



     Iterator 迭代器抽象类

1
2
3
4
5
6
7
8
9
public interface Iterator {
Object first();
Object next();
boolean isDone();
Object currentItem();
}

     Aggregate 抽象类

1
2
3
public interface Aggregate {
Iterator createIterator();
}

     ConcreteIterator 具体迭代器类, 继承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
public class ConcreteIterator implements Iterator {
private ConcreteAggregate aggregate;
private int current = 0;
public ConcreteIterator(ConcreteAggregate aggregate) {
this.aggregate = aggregate;
}
@Override
public Object first() {
return aggregate.getObject(0);
}
@Override
public Object next() {
Object ret = null;
current++;
if (current < aggregate.count()) {
ret = aggregate.getObject(current);
}
return ret;
}
@Override
public boolean isDone() {
return current >= aggregate.count();
}
@Override
public Object currentItem() {
return aggregate.getObject(current);
}
}

     ConcreteAggregate 具体聚集类, 继承 Aggregate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ConcreteAggregate implements Aggregate {
private List<Object> items = new ArrayList<>();
@Override
public Iterator createIterator() {
return new ConcreteIterator(this);
}
public int count() {
return items.size();
}
public void setObject(int index, Object value) {
items.add(index, value);
}
public Object getObject(int index) {
return items.get(index);
}
}

     客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
ConcreteAggregate a = new ConcreteAggregate();
a.setObject(0, "大鸟");
a.setObject(1, "小菜");
a.setObject(2, "行李");
a.setObject(3, "老外");
a.setObject(4, "公交内部员工");
a.setObject(5, "小偷");
Iterator i = new ConcreteIterator(a);
Object item = i.first();
while (!i.isDone()) {
System.out.println(i.currentItem() + "请买票");
i.next();
}
}

     运行结果

1
2
3
4
5
6
大鸟请买票
小菜请买票
行李请买票
老外请买票
公交内部员工请买票
小偷请买票

     “看到没有, 这就是我们的优秀售票员售票—迭代器模式的整个运作模式.”
     “大鸟, 你说为什么要用具体的迭代器 ConcreteIterator 来实现抽象的 Iterator 呢? 我觉得这里不需要抽象啊, 直接访问 ConcreteIterator 不是更好吗?”
     “哈, 你是因为刚才有一个迭代器的好处你没注意, 当你需要对聚集有多种方式遍历时, 可以考虑使用迭代器模式, 实际上, 售票员一定要从车头到车尾这样售票吗?”
     “你意思是, 他还可以从后向前遍历?”
     “当然是可以的, 你不妨再写一个实现从后往前具体迭代器类看看.”
     “好的.”

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 ConcreteIteratorDesc implements Iterator {
private ConcreteAggregate aggregate;
private int current = 0;
public ConcreteIteratorDesc(ConcreteAggregate aggregate) {
this.aggregate = aggregate;
current = aggregate.count() - 1;
}
@Override
public Object first() {
return aggregate.getObject(aggregate.count() - 1);
}
@Override
public Object next() {
Object ret = null;
current--;
if (current >= 0) {
ret = aggregate.getObject(current);
}
return ret;
}
@Override
public boolean isDone() {
return current < 0;
}
@Override
public Object currentItem() {
return aggregate.getObject(current);
}
}

     “写的不错, 这是你的客户端只要修改一个地方就可以实现反向遍历了.”

1
2
// Iterator i = new ConcreteIterator(a);
Iterator i = new ConcreteIteratorDesc(a);

     “是呀, 其实售票员完全可以更多的方式来遍历乘客, 比如从最高到最矮, 从最小到最老, 从最靓丽酷比到最猥琐龌龊.” 小菜已经开始头脑风暴.
     “神经病, 你当时你呀.” 大鸟笑骂.

20.4 Java 的迭代器的实现

     “刚才我们也说过, 实际使用当中是不需要这么麻烦的, 因为 java 框架已经为你准备好了相关的接口, 你只需要去实现就好.”
     Iterator 接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Iterator<E> {
/**
* Returns {@code true} if the iteration has more elements.
* (In other words, returns {@code true} if {@link #next} would
* return an element rather than throwing an exception.)
*
* @return {@code true} if the iteration has more elements
*/
boolean hasNext();
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration
* @throws NoSuchElementException if the iteration has no more elements
*/
E next();
// 省略删除等方法...

     再来看看我们熟悉的 foreach

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
List<String> a = new ArrayList<>();
a.add(0, "大鸟");
a.add(1, "小菜");
a.add(2, "行李");
a.add(3, "老外");
a.add(4, "公交内部员工");
a.add(5, "小偷");
for (String s : a) {
System.out.println(s + "请买车票");
}
}

     “这里用到的 foreach, 编译器其实就是做了下面的工作.”

1
2
3
4
5
Iterator<String> e = a.getIterator();
while(e.HasNext()){
System.out.println(e.next() + "请买车票");
}

     “原来, foreach 就是这两个接口实现循环遍历啊.”
     “是的, 尽管我们不需要显示的引用迭代器, 单系统本身还是通过迭代器来实现遍历的. 总的来说, 迭代器(Iterator) 模式就是分离了集合对象的遍历行为, 抽象出一个迭代器类来负责, 这样即可以做到不暴露集合的内部结构, 又可让外部代码透明的访问集合内部的数据. 迭代器模式在访问数据, 集合, 列表等数据时, 尤其是数据库的操作时, 是非常普遍的应用, 但由于它太普遍了, 所以各种高级语言都对他进行了封装, 所以反而给人感觉他不太常用了.”

~感谢捧场,您的支持将鼓励我继续创作~