1. 程式人生 > >Java設計模式之從[魔獸爭霸、星際爭霸、DOTA編隊]分析迭代器(Iterator)模式

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迭代器。它們僅僅是名稱不同而已,其用途和含義是一樣的。