1. 程式人生 > >Java設計模式--迭代器模式【Iterator Pattern】

Java設計模式--迭代器模式【Iterator Pattern】

          在Java中,因為從JDK 1.2 版本開始增加java.util.Iterator 這個介面,並逐步把Iterator 應用到各個聚集類(Collection)中,我們來看JDK 1.5 的API 幫助檔案,你會看到有一個叫java.util.Iterable 的介面,有許多介面繼承了它。java.util.Iterable 介面只有一個方法:iterator(),也就說通過iterator()這個方法去遍歷聚集類中的所有方法或屬性,基本上現在所有的高階的語言都有Iterator 這個介面或者實現,Java 已經把迭代器給我們準備了,基本上很少有專案再獨立寫迭代器了,直接使用List 或者Map 就可以完整的解決問題。

  所以我也不單獨寫迭代器模式了,網上有許多寫得好的文章,轉載過來,供大家學習。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

       在閻巨集博士的《JAVA與模式》一書中開頭是這樣描述迭代子(Iterator)模式的:

迭代子模式又叫遊標(Cursor)模式,是物件的行為模式。迭代子模式可以順序地訪問一個聚集中的元素而不必暴露聚集的內部表象(internal representation)。

聚集和JAVA聚集

多個物件聚在一起形成的總體稱之為聚集(Aggregate),聚集物件是能夠包容一組物件的容器物件。聚集依賴於聚集結構的抽象化,具有複雜化和多樣性。陣列就是最基本的聚集,也是其他的JAVA聚集物件的設計基礎。

  JAVA聚集物件是實現了共同的java.util.Collection介面的物件,是JAVA語言對聚集概念的直接支援。從1.2版開始,JAVA語言提供了很多種聚集,包括Vector、ArrayList、HashSet、HashMap、Hashtable等,這些都是JAVA聚集的例子。

迭代子模式的結構

  迭代子模式有兩種實現方式,分別是白箱聚集與外稟迭代子黑箱聚集於內稟迭代子。

白箱聚集與外稟迭代子

如果一個聚集的介面提供了可以用來修改聚集元素的方法,這個介面就是所謂的寬介面

  如果聚集物件為所有物件提供同一個介面,也就是寬介面的話,當然會滿足迭代子模式對迭代子物件的要求。但是,這樣會破壞對聚集物件的封裝。這種提供寬介面的聚集叫做白箱聚集。聚集物件向外界提供同樣的寬介面,如下圖所示:


 由於聚集自己實現迭代邏輯,並向外部提供適當的介面,使得迭代子可以從外部控制聚集元素的迭代過程。這樣一來迭代子所控制的僅僅是一個遊標而已,這種迭代子叫做遊標迭代子(Cursor Iterator)。由於迭代子是在聚集結構之外的,因此這樣的迭代子又叫做外稟迭代子(Extrinsic Iterator)

  現在看一看白箱聚集與外稟迭代子的實現。一個白箱聚集向外界提供訪問自己內部元素的介面(稱作遍歷方法或者Traversing Method),從而使外稟迭代子可以通過聚集的遍歷方法實現迭代功能。

  因為迭代的邏輯是由聚集物件本身提供的,所以這樣的外稟迭代子角色往往僅僅保持迭代的遊標位置。

  一個典型的由白箱聚集與外稟迭代子組成的系統如下圖所示,在這個實現中具體迭代子角色是一個外部類,而具體聚集角色向外界提供遍歷聚集元素的介面。


迭代子模式涉及到以下幾個角色:

  ●  抽象迭代子(Iterator)角色:此抽象角色定義出遍歷元素所需的介面。

  ●  具體迭代子(ConcreteIterator)角色:此角色實現了Iterator介面,並保持迭代過程中的遊標位置。

  ●  聚集(Aggregate)角色:此抽象角色給出建立迭代子(Iterator)物件的介面。

  ●  具體聚集(ConcreteAggregate)角色:實現了建立迭代子(Iterator)物件的介面,返回一個合適的具體迭代子例項。

  ●  客戶端(Client)角色:持有對聚集及其迭代子物件的引用,呼叫迭代子物件的迭代介面,也有可能通過迭代子操作聚集元素的增加和刪除。

原始碼

  抽象聚集角色類,這個角色規定出所有的具體聚集必須實現的介面。迭代子模式要求聚集物件必須有一個工廠方法,也就是createIterator()方法,以向外界提供迭代子物件的例項。

public abstract class Aggregate {
    /**
     * 工廠方法,建立相應迭代子物件的介面
     */
    public abstract Iterator createIterator();
}
 具體聚集角色類,實現了抽象聚集角色類所要求的介面,也就是createIterator()方法。此外,還有方法getElement()向外界提供聚集元素,而方法size()向外界提供聚集的大小等。
public class ConcreteAggregate extends Aggregate {
    
    private Object[] objArray = null;
    /**
     * 構造方法,傳入聚合物件的具體內容
     */
    public ConcreteAggregate(Object[] objArray){
        this.objArray = objArray;
    }
    
    @Override
    public Iterator createIterator() {
        
        return new ConcreteIterator(this);
    }
    /**
     * 取值方法:向外界提供聚集元素
     */
    public Object getElement(int index){
        
        if(index < objArray.length){
            return objArray[index];
        }else{
            return null;
        }
    }
    /**
     * 取值方法:向外界提供聚集的大小
     */
    public int size(){
        return objArray.length;
    }
}
抽象迭代子角色類
public interface Iterator {
    /**
     * 迭代方法:移動到第一個元素
     */
    public void first();
    /**
     * 迭代方法:移動到下一個元素
     */
    public void next();
    /**
     * 迭代方法:是否為最後一個元素
     */
    public boolean isDone();
    /**
     * 迭代方法:返還當前元素
     */
    public Object currentItem();
}
具體迭代子角色類
public class ConcreteIterator implements Iterator {
    //持有被迭代的具體的聚合物件
    private ConcreteAggregate agg;
    //內部索引,記錄當前迭代到的索引位置
    private int index = 0;
    //記錄當前聚集物件的大小
    private int size = 0;
    
    public ConcreteIterator(ConcreteAggregate agg){
        this.agg = agg;
        this.size = agg.size();
        index = 0;
    }
    /**
     * 迭代方法:返還當前元素
     */
    @Override
    public Object currentItem() {
        return agg.getElement(index);
    }
    /**
     * 迭代方法:移動到第一個元素
     */
    @Override
    public void first() {
        
        index = 0;
    }
    /**
     * 迭代方法:是否為最後一個元素
     */
    @Override
    public boolean isDone() {
        return (index >= size);
    }
    /**
     * 迭代方法:移動到下一個元素
     */
    @Override
    public void next() {

        if(index < size)
        {
            index ++;
        }
    }

}
客戶端類
public class Client {

    public void operation(){
        Object[] objArray = {"One","Two","Three","Four","Five","Six"};
        //建立聚合物件
        Aggregate agg = new ConcreteAggregate(objArray);
        //迴圈輸出聚合物件中的值
        Iterator it = agg.createIterator();
        while(!it.isDone()){
            System.out.println(it.currentItem());
            it.next();
        }
    }
    public static void main(String[] args) {
        
        Client client = new Client();
        client.operation();
    }

}

上面的例子首先建立了一個聚集類例項,然後呼叫聚集物件的工廠方法createIterator()以得到一個迭代子物件。在得到迭代子的例項後,客戶端開始迭代過程,打印出所有的聚集元素。

外稟迭代子的意義

  一個常常會問的問題是:既然白箱聚集已經向外界提供了遍歷方法,客戶端已經可以自行進行迭代了,為什麼還要應用迭代子模式,並建立一個迭代子物件進行迭代呢?

  客戶端當然可以自行進行迭代,不一定非得需要一個迭代子物件。但是,迭代子物件和迭代模式會將迭代過程抽象化,將作為迭代消費者的客戶端與迭代負責人的迭代子責任分隔開,使得兩者可以獨立的演化。在聚集物件的種類發生變化,或者迭代的方法發生改變時,迭代子作為一箇中介層可以吸收變化的因素,而避免修改客戶端或者聚集本身。

  此外,如果系統需要同時針對幾個不同的聚集物件進行迭代,而這些聚集物件所提供的遍歷方法有所不同時,使用迭代子模式和一個外界的迭代子物件是有意義的。具有同一迭代介面的不同迭代子物件處理具有不同遍歷介面的聚集物件,使得系統可以使用一個統一的迭代介面進行所有的迭代。

黑箱聚集與內稟迭代子

  如果一個聚集的介面沒有提供修改聚集元素的方法,這樣的介面就是所謂的窄介面

  聚集物件為迭代子物件提供一個寬介面,而為其他物件提供一個窄介面。換言之,聚集物件的內部結構應當對迭代子物件適當公開,以便迭代子物件能夠對聚集物件有足夠的瞭解,從而可以進行迭代操作。但是,聚集物件應當避免向其他的物件提供這些方法,因為其他物件應當經過迭代子物件進行這些工作,而不是直接操控聚集物件。



在JAVA語言中,實現雙重介面的辦法就是將迭代子類設計成聚集類的內部成員類。這樣迭代子物件將可以像聚集物件的內部成員一樣訪問聚集物件的內部結構。下面給出一個示意性的實現,說明這種雙重介面的結構時怎麼樣產生的,以及使用了雙重介面結構之後迭代子模式的實現方案。這種同時保證聚集物件的封裝和迭代子功能的實現的方案叫做黑箱實現方案

  由於迭代子是聚集的內部類,迭代子可以自由訪問聚集的元素,所以迭代子可以自行實現迭代功能並控制對聚集元素的迭代邏輯。由於迭代子是在聚集的結構之內定義的,因此這樣的迭代子又叫做內稟迭代子(Intrinsic Iterator)。

  為了說明黑箱方案的細節,這裡給出一個示意性的黑箱實現。在這個實現裡,聚集類ConcreteAggregate含有一個內部成員類ConcreteIterator,也就是實現了抽象迭代子介面的具體迭代子類,同時聚集並不向外界提供訪問自己內部元素的方法。


 原始碼

  抽象聚集角色類,這個角色規定出所有的具體聚集必須實現的介面。迭代子模式要求聚集物件必須有一個工廠方法,也就是createIterator()方法,以向外界提供迭代子物件的例項。

public abstract class Aggregate {
    /**
     * 工廠方法,建立相應迭代子物件的介面
     */
    public abstract Iterator createIterator();
}
抽象迭代子角色類
public interface Iterator {
    /**
     * 迭代方法:移動到第一個元素
     */
    public void first();
    /**
     * 迭代方法:移動到下一個元素
     */
    public void next();
    /**
     * 迭代方法:是否為最後一個元素
     */
    public boolean isDone();
    /**
     * 迭代方法:返還當前元素
     */
    public Object currentItem();
}
     具體聚集角色類,實現了抽象聚集角色所要求的介面,也就是createIterator()方法。此外,聚集類有一個內部成員類ConcreteIterator,這個內部類實現了抽象迭代子角色所規定的介面;而工廠方法createIterator()所返還的就是這個內部成員類的例項。
public class ConcreteAggregate extends Aggregate {
    
    private Object[] objArray = null;
    /**
     * 構造方法,傳入聚合物件的具體內容
     */
    public ConcreteAggregate(Object[] objArray){
        this.objArray = objArray;
    }
    
    @Override
    public Iterator createIterator() {

        return new ConcreteIterator();
    }
    /**
     * 內部成員類,具體迭代子類
     */
    private class ConcreteIterator implements Iterator
    {
        //內部索引,記錄當前迭代到的索引位置
        private int index = 0;
        //記錄當前聚集物件的大小
        private int size = 0;
        /**
         * 建構函式
         */
        public ConcreteIterator(){
            
            this.size = objArray.length;
            index = 0;
        }
        /**
         * 迭代方法:返還當前元素
         */
        @Override
        public Object currentItem() {
            return objArray[index];
        }
        /**
         * 迭代方法:移動到第一個元素
         */
        @Override
        public void first() {
            
            index = 0;
        }
        /**
         * 迭代方法:是否為最後一個元素
         */
        @Override
        public boolean isDone() {
            return (index >= size);
        }
        /**
         * 迭代方法:移動到下一個元素
         */
        @Override
        public void next() {

            if(index < size)
            {
                index ++;
            }
        }
    }
}
客戶端類
public class Client {

    public void operation(){
        Object[] objArray = {"One","Two","Three","Four","Five","Six"};
        //建立聚合物件
        Aggregate agg = new ConcreteAggregate(objArray);
        //迴圈輸出聚合物件中的值
        Iterator it = agg.createIterator();
        while(!it.isDone()){
            System.out.println(it.currentItem());
            it.next();
        }
    }
    public static void main(String[] args) {
        
        Client client = new Client();
        client.operation();
    }

}

上面的例子首先建立了一個聚集類例項,然後呼叫聚集物件的工廠方法createIterator()以得到一個迭代子物件。在得到迭代子的例項後,客戶端開始迭代過程,打印出所有的聚集元素。

主動迭代子和被動迭代子

  主動迭代子和被動迭代子又稱作外部迭代子和內部迭代子。

  所謂主動(外部)迭代子,指的是由客戶端來控制迭代下一個元素的步驟,客戶端會明顯呼叫迭代子的next()等迭代方法,在遍歷過程中向前進行。

  所謂被動(內部)迭代子,指的是由迭代子自己來控制迭代下一個元素的步驟。因此,如果想要在迭代的過程中完成工作的話,客戶端就需要把操作傳遞給迭代子,迭代子在迭代的時候會在每個元素上執行這個操作,類似於JAVA的回撥機制。

  總體來說外部迭代器比內部迭代器要靈活一些,因此我們常見的實現多屬於主動迭代子。

靜態迭代子和動態迭代子

  ●  靜態迭代子由聚集物件建立,並持有聚集物件的一份快照(snapshot),在產生後這個快照的內容就不再變化。客戶端可以繼續修改原聚集的內容,但是迭代子物件不會反映出聚集的新變化。

  靜態迭代子的好處是它的安全性和簡易性,換言之,靜態迭代子易於實現,不容易出現錯誤。但是由於靜態迭代子將原聚集複製了一份,因此它的短處是對時間和記憶體資源的消耗。

  ●  動態迭代子則與靜態迭代子完全相反,在迭代子被產生之後,迭代子保持著對聚集元素的引用,因此,任何對原聚集內容的修改都會在迭代子物件上反映出來。

  完整的動態迭代子不容易實現,但是簡化的動態迭代子並不難實現。大多數JAVA設計師遇到的迭代子都是這種簡化的動態迭代子。為了說明什麼是簡化的動態迭代子,首先需要介紹一個新的概念:Fail Fast

Fail Fast

  如果一個演算法開始之後,它的運算環境發生變化,使得演算法無法進行必需的調整時,這個演算法就應當立即發出故障訊號。這就是Fail Fast的含義。

  如果聚集物件的元素在一個動態迭代子的迭代過程中發生變化時,迭代過程會受到影響而變得不能自恰。這時候,迭代子就應當立即丟擲一個異常。這種迭代子就是實現了Fail Fast功能的迭代子。

Fail Fast在JAVA聚集中的使用

  JAVA語言以介面java.util.Iterator的方式支援迭代子模式,Collection介面要求提供iterator()方法,此方法在呼叫時返還一個Iterator型別的物件。而作為Collection介面的子型別,AbstractList類的內部成員類Itr便是實現Iterator介面的類。

  Itr類的原始碼如下所示

private class Itr implements Iterator<E> {
        /**
         * Index of element to be returned by subsequent call to next.
         */
        int cursor = 0;

        /**
         * Index of element returned by most recent call to next or
         * previous.  Reset to -1 if this element is deleted by a call
         * to remove.
         */
        int lastRet = -1;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;

        public boolean hasNext() {
                return cursor != size();
        }

        public E next() {
                checkForComodification();
            try {
            E next = get(cursor);
            lastRet = cursor++;
            return next;
            } catch (IndexOutOfBoundsException e) {
            checkForComodification();
            throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet == -1)
            throw new IllegalStateException();
                checkForComodification();

            try {
            AbstractList.this.remove(lastRet);
            if (lastRet < cursor)
                cursor--;
            lastRet = -1;
            expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
            throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
        }
    }

從Itr類的原始碼中可以看到,方法checkForComodification()會檢查聚集的內容是否剛剛被外界直接修改過(不是通過迭代子提供的方法修改的)。如果在迭代開始後,聚集的內容被外界繞過迭代子物件而直接修改的話,這個方法會立即丟擲ConcurrentModificationException()異常。

  這就是說,AbstractList.Itr迭代子是一個Fail Fast的迭代子。

迭代子模式的優點

  (1)迭代子模式簡化了聚集的介面。迭代子具備了一個遍歷介面,這樣聚集的介面就不必具備遍歷介面。

  (2)每一個聚集物件都可以有一個或多個迭代子物件,每一個迭代子的迭代狀態可以是彼此獨立的。因此,一個聚集物件可以同時有幾個迭代在進行之中。

  (3)由於遍歷演算法被封裝在迭代子角色裡面,因此迭代的演算法可以獨立於聚集角色變化。