迭代器模式提供了一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部实现。
有过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- 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
- 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- buns = bun.getBuns(); 5 Item[] drinks = drink.getDrinks(); 6 7 //输出包子 8 for(int i=0; i
){ 9 Item item = buns.get(i); 10 System.out.println(item.getName() + ", " + item.getPrice()); 11 } 12 13 //输出饮品 14 for(int i=0; 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- items; 3 int position; 4 5 public BunIterator(ArrayList
- 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- 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内部的迭代器实现后,我们不再需要编写自己的迭代器,这里是为了展示迭代器的原理才自己实现。