Java设计模式之迭代器模式

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


网上大多数关于迭代器模式的文章,讨论都是建立在一个List或是其他可迭代对象之上的。这样有一个问题,因为都是可迭代的对象,在迭代的过程中我们无法深入了解到迭代器模式能够为我们解决什么样的问题。下面有一个实例,通过这个实例可能可以说明迭代器模式的威力。

1.实例背景

  有两家餐厅A和B,餐厅A是一家早餐餐厅店,而餐厅B是一家午餐餐厅店。

  现在两个餐厅的大Boss决定将两家餐厅进行合并。在餐厅A中,菜单的逻辑实现是基于ArrayList的,而餐厅B的菜单逻辑实现则是基于数组的。如果去修改任何一家餐厅的菜单实现,都可能会引发一些不必要的修改,且这种修改可能还会导致不必要的Bug,所以A和B都不愿意去修改菜单的原始实现。

  现在的需求是,在不改变两家餐厅对菜单的实现上,再方便地对菜单进行遍历。

                                                                      -- 摘自《Head Frist设计模式》

2.思路分析

  这个有什么难的?ArrayList和数组都是可循环遍历的对象,那么我们不是可以依次对这两个数组进行循环遍历么?

  不可否认,这是一种最为朴素的实现逻辑,而且易懂。可是,这种方式真的是好的么?如果现在有100家餐厅呢?我们希望的是能够更方便、更优雅地进行遍历菜单,我们希望在后期的项目中,代码的维护性更高,而不是一直“烂”下去。

  我们想如果我们的ArrayList和数组可以有一个公共的接口,那么我们就可以通过这个公共的接口进行迭代,这样一来代码的复用性岂不是更高了么?可是,如何找到这样的一个接口呢?在学习《Effective Java》这本神书的“类和接口”一单时,就了解到我们的程序中,复合优于继承。所以,这里我们可以将ArrayList和数组分别组合到不同的菜单中去,这样对菜单的遍历就是对ArrayList或是数组的遍历。

  因为ArrayList自身就包含了迭代器,所以这里我们不需要为ArrayList创建迭代器。而在数组中则没有这样的迭代逻辑,所以我们要为数组额外创建迭代器。


3.类图

  根据上面的分析,我们可以画出如下类图:

Java设计模式之迭代器模式

图-1 迭代器模式类图

4.逻辑实现

(1)公共接口编写

  在类图中的第一个位置就留给了Menu这个接口了,添加此接口的目的在于提高代码的可复用性。这一点在第(4)点迭代逻辑中会有所体现。公共接口代码如下:

[java] view plain copy
  1. public interface Menu {  
  2.     public Iterator<MenuItem> createIterator();  
  3. }  

 

(2)迭代器的创建

  在上面的类图中我们可以找到BreakfastMenu和LunchMenu两个菜单类,它们都是实现了Menu接口的。可是,因为它们包含了不同的容器对象(BreakfastMenu包含了ArrayList,LunchMenu包含了数组),所以在创建迭代器的时候就会有所不同。

  先来看看BreakfastMenu创建迭代器的逻辑代码吧,如下:

[java] view plain copy
  1. @Override  
  2.     public Iterator<MenuItem> createIterator() {  
  3.         return menuItems.iterator();  
  4.     }  
  因为ArrayList自身就包含了迭代器的实现,所以这里就可以直接返回ArrayList的迭代器。而数组没有迭代器的实现部分,所以与上面的创建方式会有所不同,如下:

[java] view plain copy
  1. @Override  
  2.     public Iterator<MenuItem> createIterator() {  
  3.         return new LunchIterator(menuItems);  
  4.     }  

(3)自定义迭代器

  由于数组本身不具备迭代的功能,所以我们就需要对其进行扩展。可是,如果想要“迭代”数组,其根本实现还是要依赖于数组的循环遍历。因为数组只有这一种方式可以依次提取元素。在迭代器中有两个核心方法:hasNext()和next()。所以,我们就利用这两个方法变相实现对数组的迭代。代码如下:

[java] view plain copy
  1. public class LunchIterator implements Iterator<MenuItem> {  
  2.   
  3.     private MenuItem[] menuItems = null;  
  4.     private int position = 0;  
  5.       
  6.     public LunchIterator(MenuItem[] _menuItems) {  
  7.         this.menuItems = _menuItems;  
  8.     }  
  9.       
  10.     @Override  
  11.     public boolean hasNext() {  
  12.         if (menuItems == null) {  
  13.             return false;  
  14.         }  
  15.           
  16.         return position < menuItems.length;  
  17.     }  
  18.   
  19.     @Override  
  20.     public MenuItem next() {  
  21.         MenuItem menuItem = menuItems[position];  
  22.         position++;  
  23.         return menuItem;  
  24.     }  
  25. }  
  这里有一个额外定义的变量position,它是用来进行元素索引的,我们需要通过此变量提取元素,和判别迭代完成。

 

(4)迭代逻辑

  这里说的迭代逻辑是针对迭代器之外,客户端的实现逻辑。假定我们有一个女服务员,她可以打印出客户所需要的菜单,而不用关心此菜单的实现方式。

[java] view plain copy
  1. public class Waitress {  
  2.   
  3.     private Iterator<MenuItem> iterator = null;  
  4.     private Menu menu = null;  
  5.       
  6.     public Waitress(Menu _menu) {  
  7.         this.menu = _menu;  
  8.     }  
  9.       
  10.     public void printMenu() {  
  11.         System.out.println("\n菜单:");  
  12.         iterator = menu.createIterator();  
  13.         do {  
  14.             System.out.println(iterator.next());  
  15.         } while (iterator.hasNext());  
  16.     }  
  17. }  
  18. 资源转载于博客:http://blog.****.net/lemon_tree12138/article/details/50799562

     最后总结一下迭代器模式的优点:

  1. 迭代子模式简化了聚集的接口。迭代子具备了一个遍历接口,这样聚集的接口就不必具备遍历接口。
  2. 每一个聚集对象都可以有一个或多个迭代子对象,每一个迭代子的迭代状态可以是彼此独立的。因此,一个聚集对象可以同时有几个迭代在进行之中。
  3. 由于遍历算法被封装在迭代子角色里面,因此迭代的算法可以独立于聚集角色变化。