前言
我们有很多种方法可以把对象集中到一个集合中,比如列表、堆栈、散列表中。每种集合都有自己的特点和使用时机,但都有一个共同的需求:用户想要遍历这些对象。同时我们并不想用户看到集合的实现,本文将讲解如何让用户遍历对象又无法窥视存储对象的方式。
正文
1、先来看一个实际案例
有2个餐厅,它们分别用列表和数组来保存各自的菜品:
public class BreakfastMenu {
ArrayList breakfastItems;
...
ArrayList getBreakfastItems() {
return breakfastItems; // 这里会暴露类内部的数据结构
}
}
public class DinerMenu {
MenuItem[] dinerItems;
...
MenuItem[] getDinerItems() {
return dinerItems; // 这里会暴露类内部的数据结构
}
}
我们想同时打印这2份菜单中的菜品,会变得复杂。我们需要首先拿到2个菜单类的变量,然后再用不同的方式遍历这2个变量。如果有第3份菜单,就需要三个循环来遍历。
for (int i = 0; i < breakfastItems.size(); i++) {
MenuItem menuItem = breakfastItems.get(i);
}
for (int i = 0; i < dinerMenu.length; i++) {
MenuItem menuItem = dinerMenu[i];
}
2、如何优化
当然有优化方法了,设计模式最重要的思想就是封装变化的部分。很明显,上面发生的变化是:由不同的集合类型所造成的遍历。我们来看看怎么封装:我们创建一个对象,称为“迭代器”(Iterator),用它来封装“遍历集合每个对象的过程”。
我们先在ArrayList上试试,我们先从菜单对象breakfastMenu获得一个菜单迭代器,用户只需要调用迭代器的hasNext()和next()方法。
Iterator iterator = breakfastMenu.createIterator();
while (iterator.hasNext()) {
MenuItem menuItem = iterator.next();
}
再在数组上试试:
Iterator iterator = dinerMenu.createIterator();
while (iterator.hasNext()) {
MenuItem menuItem = iterator.next(); // 和上面的遍历一模一样
}
3、迭代器模式
看来我们的优化奏效了,这就是大名鼎鼎的迭代器模式。它依赖一个叫做迭代器的接口。下面是一个最基本的迭代器接口。
interface Iterator {
boolean hasNext(); // 集合中是否还有更多元素
Object next(); // 返回下一个对象
}
有了这个接口,我们可以为各种集合创建迭代器。
public class DinerMenuIterator implements Iterator {
MenuItem[] items; // 作为数组的迭代器,需要持有数组类型
public boolean hasNext() {
// 判断items中还有没有元素
}
public Object next() {
// 判断items中下一个元素
}
}
有了数组迭代器,我们就可以改写菜单类了。
public class DinerMenu {
MenuItem[] dinerItems;
...
// 现在就不用返回实例对象了,避免了暴露类的内部结构
// MenuItem[] getDinerItems() {
// return dinerItems;
// }
// 现在只需要给用户返回一个迭代器对象
public Iterator createIterator() {
return new DinerMenuIterator(dinerItems);
}
}
现在我们来正式定义一下迭代器模式
迭代器提供一种顺序访问集合元素的方法,而又不暴露其内部结构。
这样做很有意义,迭代器模式把遍历元素的职责交给迭代器,而不是集合对象本身。这不仅让集合类的实现变得简洁,也可以让集合更专注于它应该关注的事情上(也就是管理对象元素)。下面是迭代器模式的类图:
4、单一职责
设计原则:一个类应该只有一个引起变化的原因
“内聚”这个术语用来度量一个类或模块达到单一目的或责任。当一个类或模块被设计成只支持一组相关的功能时,我们说它“高内聚”,更容易维护。
5、JDK中的迭代器模式
JDK中已经给我们提供了迭代器接口,和上面的非常类似,用法也基本一致。各种集合都通过迭代器完成遍历,我们来看下ArrayList是怎么实现的。
ArrayList中的iterator()方法返回一个迭代器对象。
public Iterator<E> iterator() {
return new Itr();
}
Itr类是ArrayList中的内部类,大多数集合都是用内部类来实现迭代器的。
private class Itr implements Iterator<E> {
...
public boolean hasNext() {
...
}
public E next() {
...
}
}
总结
迭代器模式很好,一方面提供了统一简洁的集合遍历方式,另一方面避免暴露集合内部结构。
集合遍历方式也在持续升级,后面JDK1.5又推出了“foreach”语法糖。JDK1.8又推出了forEach()方法继续简化遍历形式,请参考文章:Java8新特性:集合遍历forEach方法
,我们甚至不用请求迭代器了。