迭代器模式提供了一種方法順序訪問一個聚合對象中的各個元素,而又不暴露其內部實現。
有過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內部的迭代器實現后,我們不再需要編寫自己的迭代器,這里是為了展示迭代器的原理才自己實現。