迭代器模式

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

      有过Java编程经验的人对这种模式应该比较熟悉,因为Java内置的许多集合类型:List、Set、Map等都提供了迭代器接口,可以使用统一的方式遍历集合中的元素。下面将通过一个例子说明迭代器的使用场景,并了解一下迭代器模式的原理。

      包子店卖的有包子和饮品,对于包子和饮品的每一个条目,我们用Item来表示,Item只包含name和price两个字段:

 1 public class Item {
 2   private String name;
 3   private double price;
 4 
 5   public Item(String name, double price){
 6     this.name = name;
 7     this.price = price;
 8   }
 9 
10   // getters and setters ...
11 }

      由于包子会不定期更新,所以用一个ArrayList来存储目前所有的包子类别:

 1 public class Bun {
 2   ArrayList<Item> buns;
 3 
 4   public Bun(){
 5     buns = new ArrayList<>();
 6 
 7     addBun("鲜肉包子", 1.5);
 8     addBun("香菇青菜包子",  1);
 9     addBun("鱼香肉丝包子",  1.5);
10   }
11 
12   private void addBun(String name, double price){
13     Item item = new Item(name, price);
14     buns.add(item);
15   }
16 
17   public ArrayList<Item> getBuns(){
18     return buns;
19   }
20 }

      而饮品则比较固定,一般不会增加新的类型,所以就假设固定成5种好了,对于这种需求,或许我们会选择使用数组来实现:

 1 public class Drink {
 2   Item[] drinks;
 3   int position;
 4   private static final int MAX_SIZE = 5;
 5 
 6   public Drink(){
 7     drinks = new Item[MAX_SIZE];
 8     position = 0;
 9 
10     addDrink("豆浆", 2);
11     addDrink("八宝粥", 2);
12     addDrink("牛奶", 2.5);
13     addDrink("银耳汤", 3);
14     addDrink("豆腐脑", 2);
15   }
16 
17   private void addDrink(String name, double price){
18     Item item = new Item(name, price);
19     if(position >= MAX_SIZE){
20       System.err.println("饮品已经满了。。。");
21     }else{
22       drinks[position++] = item;
23     }
24   }
25 
26   public Item[] getDrinks(){
27     return drinks;
28   }
29 }

      那么,当我们需要输出早餐店里的所有包子和饮品的时候,需要怎么写呢?我们发现包子和饮品的底层存储不一样,或许都改成ArrayList会简单很多,但是由于代码已经写好了,其他很多地方都会使用上面的代码,所以冒险修改不是一个好选择,只能麻烦一些,针对两种情况分别处理:

 1   public void printItems(){
 2     Bun bun = new Bun();
 3     Drink drink = new Drink();
 4     ArrayList<Item> buns = bun.getBuns();
 5     Item[] drinks = drink.getDrinks();
 6 
 7     //输出包子
 8     for(int i=0; i<buns.size(); i++){
 9       Item item = buns.get(i);
10       System.out.println(item.getName() + ", " + item.getPrice());
11     }
12 
13     //输出饮品
14     for(int i=0; i<drinks.length; i++){
15       System.out.println(drinks[i].getName() + ", " + drinks[i].getPrice());
16     }
17   }

      输出如下:

鲜肉包子, 1.5
香菇青菜包子, 1.0
鱼香肉丝包子, 1.5
豆浆, 2.0
八宝粥, 2.0
牛奶, 2.5
银耳汤, 3.0
豆腐脑, 2.0

      这里的打印逻辑的实现有几个问题:①打印方法需要知道包子和饮品的底层实现细节,这不满足封装的要求;②打印的逻辑不能扩展,如果包子店增加了馒头类型,而某位程序员打算使用Set来存储所有的馒头,那么打印方法必须要同步修改。因此,我们要做的就是隐藏底层的逻辑,对外提供统一的遍历接口,不管底层采用什么实现,对外保持一致就行。

      为了保持遍历接口的简单性,我们不打算加入太多的逻辑,具体做法是定义一个迭代器接口,包含next()和hasNext()两个方法:

1 public interface Iterator {
2   boolean hasNext();
3   Item next();
4 }

      为包子和饮品分别定义对应的迭代器:

 1 public class BunIterator implements Iterator{
 2   ArrayList<Item> items;
 3   int position;
 4 
 5   public BunIterator(ArrayList<Item> items){
 6     this.items = items;
 7     position = 0;
 8   }
 9 
10   @Override
11   public boolean hasNext() {
12     if(items == null || position >= items.size()){
13       return false;
14     }else{
15       return true;
16     }
17   }
18 
19   @Override
20   public Item next() {
21     return items.get(position++);
22   }
23 
24 
25 public class DrinkIterator implements Iterator{
26   Item[] items;
27   int position;
28 
29   public DrinkIterator(Item[] items){
30     this.items = items;
31     position = 0;
32   }
33 
34   @Override
35   public boolean hasNext() {
36     if(position >= items.length || items[position] == null){
37       return false;
38     }else{
39       return true;
40     }
41   }
42 
43   @Override
44   public Item next() {
45     return items[position++];
46   }
47 }

      修改包子和饮品类,只对外提供creatorIterator方法:

 1 public class Bun{
 2   ArrayList<Item> buns;
 3 
 4   public Bun(){
 5     buns = new ArrayList<>();
 6 
 7     addBun("鲜肉包子", 1.5);
 8     addBun("香菇青菜包子",  1);
 9     addBun("鱼香肉丝包子",  1.5);
10   }
11 
12   private void addBun(String name, double price){
13     Item item = new Item(name, price);
14     buns.add(item);
15   }
16 
17   public Iterator creatorIterator(){
18     return new BunIterator(buns);
19   }
20 }
21 
22 
23 public class Drink {
24   Item[] drinks;
25   int position;
26   private static final int MAX_SIZE = 5;
27 
28   public Drink(){
29     drinks = new Item[MAX_SIZE];
30     position = 0;
31 
32     addDrink("豆浆", 2);
33     addDrink("八宝粥", 2);
34     addDrink("牛奶", 2.5);
35     addDrink("银耳汤", 3);
36     addDrink("豆腐脑", 2);
37   }
38 
39   private void addDrink(String name, double price){
40     Item item = new Item(name, price);
41     if(position >= MAX_SIZE){
42       System.err.println("饮品已经满了。。。");
43     }else{
44       drinks[position++] = item;
45     }
46   }
47 
48   public Iterator creatorIterator(){
49     return new DrinkIterator(drinks);
50   }
51 }

      接下来使用迭代器写一个新的打印方法:

 1 public class TestIterator {
 2 
 3   public static void main(String[] args){
 4     TestIterator test = new TestIterator();
 5     test.printItemsWithIterator();
 6   }
 7 
 8   public void printItemsWithIterator(){
 9     Bun bun = new Bun();
10     Drink drink = new Drink();
11     Iterator bunIterator = bun.creatorIterator();
12     Iterator drinkIterator = drink.creatorIterator();
13     printItemsWithIterator(bunIterator);
14     printItemsWithIterator(drinkIterator);
15   }
16 
17   public void printItemsWithIterator(Iterator iterator){
18     while(iterator.hasNext()){
19       Item item = iterator.next();
20       System.out.println(item.getName() + ", " + item.getPrice());
21     }
22   }
23 }

      输出如下:

鲜肉包子, 1.5
香菇青菜包子, 1.0
鱼香肉丝包子, 1.5
豆浆, 2.0
八宝粥, 2.0
牛奶, 2.5
银耳汤, 3.0
豆腐脑, 2.0

      仍然能够完成打印任务,而且使用迭代器模式使得代码简洁了许多,也更容易维护。

      以上简介了迭代器模式的用法,实际上有了Java内部的迭代器实现后,我们不再需要编写自己的迭代器,这里是为了展示迭代器的原理才自己实现。

posted @ 2019-10-06 23:57  纳兰小依  阅读(645)  评论(0编辑  收藏  举报