Java設計模式之從[魔獸爭霸、星際爭霸、DOTA編隊]分析迭代器(Iterator)模式
在即時戰略遊戲、DOTA中,我們可以多選我們部隊,讓他們組成一個隊伍。在星際1、魔獸3中,一支隊伍的最大單位數量為12個,當我們選中一支隊伍後,可以命令他們集體朝著哪個方向移動或者進攻,而不用一個一個控制我們的單位。在程式中,我們是如何實現向這支隊伍“群發”命令的呢?最開始想到的就是迴圈——把隊伍中的每一個單位加入一個列表中,寫一個for迴圈,依次訪問這個列表中的每一個成員,讓它們接收命令。
而這一次,我將介紹一下迭代器模式。迭代器對於很多人來說是一個很熟悉的名詞,它又叫做遊標模式,它可以提供一種順序訪問一個集合物件中每一個元素,而又不暴露此物件的內部表示的方法。簡單來說,對於一個集合Team,我們只需要通過next來獲取集合中的下一個元素,以及用hasNext來判斷是否集合還存在下一個元素,就可以遍歷到該Team集合的所有元素了。
在下面這個例子中,假設我有3個英雄單位,分別叫做路西法、卡爾和奈文摩爾,現在我把他們加入了一支隊伍,並且遍歷輸出他們的名字:
import java.util.Arrays; import java.util.List; interface Iterator<T>{ T next(); boolean hasNext(); } class Unit{ private String name; public Unit(String name){ this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } class Team<T>{ List<T> members ; int cursor = 0; public Iterator<T> getIterator(){ return new Iterator<T>(){ public T next(){ T result = members.get(cursor); cursor ++; return result; } public boolean hasNext(){ return cursor < members.size(); } }; } public Team(List<T> members){ this.members = members; } } class IteratorExample { public static void main(String[] args) throws InterruptedException { Unit u1 = new Unit("路西法"); Unit u2 = new Unit("卡爾"); Unit u3 = new Unit("奈文摩爾"); List<Unit> units = Arrays.asList(u1, u2, u3); Team<Unit> team = new Team<Unit>(units); Iterator<Unit> iter = team.getIterator(); //獲得一個新的迭代器 while (iter.hasNext()){ System.out.println(iter.next().getName()); } } }
可以看到,迭代器介面主要定義了兩個方法:next()是返回下一個元素,hasNext()返回是否有下一個元素。在Team類中,我們通過getIterator()返回了一個匿名的迭代器,它的內部有一個cursor,其實就是Team類中members列表的遊標,每使用迭代器的next(),cursor就會自增1。我們可以在main方法中看到,通過while、hasNext()和next()的配合使用,我們可以遍歷到team中的每一個元素,因此程式輸出的結果為:
路西法
卡爾
奈文摩爾
下面來解釋幾個關鍵的問題:1、為什麼要將Iterator寫為內部類?因為我們希望每次使用getIterator返回的都是一個新的迭代器(即每一個迭代器的cursor都是從0開始)。
2、這是否是一個健壯的迭代器?答案:不是。一個健壯的迭代器要求保證插入和刪除操作都不會干擾遍歷,而在此迭代器中,如果對members進行了新增、刪除操作,由於cursor的值不會因此改變,所以會導致遍歷被幹擾(如在cursor位置之前插入了一個元素,則遍歷的時候,會輸出兩個一模一樣的元素)。迭代器在Java、C#中用在foreach語句中最為廣泛,且它們不允許在用foreach語句進行迭代器遍歷的時候對原集合的物件進行新增、刪除元素的操作,因為這樣會干擾迭代器。
3、如果想在Java中為一個類實現相容foreach語句,這個類必須要繼承Iterable<T>介面,它會要求這個類必須有一個iterator()方法來返回一個Iterator<T>迭代器。同理,在C#中如果要實現一個類相容foreach語句,必須要繼承IEnumerable<T>介面,它要求此類必須實現GetEnumerator()返回一個IEnumerator迭代器。它們僅僅是名稱不同而已,其用途和含義是一樣的。