迭代器模式提供了一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部实现。
有过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内部的迭代器实现后,我们不再需要编写自己的迭代器,这里是为了展示迭代器的原理才自己实现。