java設(shè)計模式-迭代子模式(Iterator)

定義

迭代子模式又叫游標(biāo)(Cursor)模式,是對象的行為模式。迭代子模式可以順序的訪問一個聚集中的元素而不必暴露聚集的內(nèi)部表象(internal representation)。

聚集和JAVA聚集

多個對象聚在一起形成的總體稱為聚集(Aggregate),聚集對象是能夠包含一組對象的容器對象。聚集依賴于聚集結(jié)構(gòu)的抽象化,具有復(fù)雜化和多樣性。數(shù)組就是最基本的聚集,也是其他JAVA聚集對象的設(shè)計基礎(chǔ)。

Java聚集對象時實現(xiàn)了共同的java.util.Collection接口的對象,是Java語言對聚集概念的直接支持。從1.2版開始,Java語言提供了很多種聚集,包括VectorArrayListHashSetHashMapHashtable等,這些都是Java聚集的例子。

迭代子模式的結(jié)構(gòu)

迭代子模式具有兩種實現(xiàn)方式,分別為白箱聚集于外稟迭代子黑箱聚集于內(nèi)稟迭代子

白箱聚集于外稟迭代子

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

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

白箱聚集

由于聚集自己實現(xiàn)迭代邏輯,并向外部提供適當(dāng)?shù)慕涌冢沟玫涌梢詮耐獠靠刂凭奂氐牡^程。這樣一來迭代子所控制的僅僅是一個游標(biāo)而已,這種迭代子叫做游標(biāo)迭代子(Cursor Iterator)。由于迭代子是在聚集結(jié)構(gòu)之外的,因此這樣的迭代子又叫做外稟迭代子(Extrinsic Iterator)

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

因為迭代的邏輯是由聚集對象本身提供的,所以這樣的外稟迭代子角色往往僅僅保持迭代的游標(biāo)位置。

一個典型的由白箱聚集于外稟迭代子組成的系統(tǒng)如下圖所示,在這個實現(xiàn)中具體迭代子角色是一個外部類,而具體聚集對象角色向外界提供遍歷聚集元素的借口。

由白箱聚集于外稟迭代子組成的系統(tǒng)

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

  • 抽象迭代子角色(Iterator):此抽象角色定義出遍歷的對象所需的借口。
  • 具體迭代子角色(ConcreteIterator):此角色實現(xiàn)了Iterator接口,并保持迭代過程中的游標(biāo)位置。
  • 聚集角色(Aggregate):此抽象角色給出創(chuàng)建迭代子Iterator對象的接口。
  • 具體聚集角色(ConcreteAggregate):實現(xiàn)了創(chuàng)建迭代子Iterator對象的接口,返回一個合適的具體迭代子對象實例。
  • 客戶端角色(Client):持有對聚集及其迭代子對象的引用,調(diào)用迭代子對象的迭代接口,也有可能通過迭代子操作聚集元素的增加和刪除。

示例代碼

抽象聚集角色類,這個角色規(guī)定出所有的聚集必須實現(xiàn)的接口。迭代子模式要求聚集對象必須有一個工廠方法,也就是createIterator()方法,以向外界提供迭代子對象的實例。

public abstract class Aggregate {
    /**
     * 工廠方法,創(chuàng)建相應(yīng)迭代子對象的接口
     * @return
     */
    public abstract Iterator createIterator();
}

聚提聚集角色類,實現(xiàn)了抽象聚集角色類所要求的借口,也就是createIterator()方法。此外,還有方法getElement()向外界提供聚集元素,而方法size()向外界提供聚集的大小等。

public class ConcreteAggregate extends Aggregate {
    private Object[] objArray = null;
    /**
     * 構(gòu)造方法,傳入聚合對象的具體內(nèi)容
     * @param objArray
     */
    public ConcreteAggregate(Object[] objArray) {
        this.objArray = objArray;
    }
    @Override
    public Iterator createIterator() {
        return new ConcreteIterator(this);
    }
    
    /**
     * 取值方法,向外界提供聚集元素
     * @param index
     * @return
     */
    public Object getElement(int index) {
        if (index < objArray.length) {
            return objArray[index];
        } else {
            return null;
        }
    }
     /**
      * 取值方法,向外界提供聚集的大小
      * @return
      */
    public int size() {
        return objArray.length;
    }
}

抽象迭代子角色類

public interface Iterator {
    /**
     * 迭代方法,移動至第一個元素
     */
    public void first();
    /**
     * 迭代方法,移動至下一個元素
     */
    public void next();
    /**
     * 迭代方法,是否為最后一個元素
     */
    public boolean isDone();
    /**
     * 迭代方法,返回當(dāng)前元素
     */
    public Object currentItem();
}

具體迭代子角色類

public class ConcreteIterator implements Iterator {
    /**
     * 持有被迭代的具體的聚合對象
     */
    private ConcreteAggregate agg;
    /**
     * 內(nèi)部索引,記錄當(dāng)前迭代到的索引位置
     */
    private int index = 0;
    /**
     * 記錄當(dāng)前索引對象的大小
     */
    private int size = 0;
    
    /**
     * 構(gòu)造函數(shù),傳入具體的聚合對象,并獲取聚合對象大小
     * @param agg
     */
    public ConcreteIterator(ConcreteAggregate agg) {
        this.agg = agg;
        this.size = agg.size();
        this.index = 0;
    }
    /**
     * 移動至第一個元素
     */
    public void first() {
        this.index = 0;
    }

    /**
     * 迭代方法,移動到下一個元素
     */
    public void next() {
        if (this.index < this.size) {
            this.index++;
        }
    }

    /**
     * 迭代方法,是否是最后一個元素
     */
    public boolean isDone() {
        return (this.index >= this.size);
    }

    /**
     * 迭代方法,返還當(dāng)前位置元素
     */
    public Object currentItem() {
        return this.agg.getElement(this.index);
    }
}

上面的例子首先創(chuàng)建了一個聚集類角色,然后調(diào)用聚集類對象的工廠方法createIterator()以得到一個迭代子對象。在得到迭代子對象后,客戶端進(jìn)行迭代過程處理,打印出所有的聚集元素。

外稟迭代子的意義

一個常常會問的為題就是:既然白箱聚集已經(jīng)向外界提供了遍歷方法,客戶端已經(jīng)可以自行進(jìn)行迭代了,為什么還要應(yīng)用迭代子模式,并創(chuàng)建一個迭代子對象進(jìn)行迭代呢?

客戶端當(dāng)然可以自行進(jìn)行迭代,不一定非要一個迭代子對象。但是,迭代子對象和迭代模式會將迭代過程抽象化,將作為迭代消費者的客戶端和迭代負(fù)責(zé)人的迭代子責(zé)任分隔開,使得兩者都可以獨立的煙花。在聚集對象的種類發(fā)生變化,或者迭代的方法發(fā)生改變時,迭代子作為一個中間層可以吸收變化的因素,從而避免修改客戶端或者聚集本身。

此外,如果系統(tǒng)同時需要針對幾個不同的聚集對象進(jìn)行迭代,而這些聚集對象所提供的遍歷方法有所不同是,使用迭代子模式和一個外界的迭代子對象是有意義的。具有同一個迭代接口的不同迭代子對象處理具有不同遍歷接口的聚集對象,使得系統(tǒng)可以使用一個統(tǒng)一的迭代接口進(jìn)行所有的迭代。

黑箱聚集與內(nèi)稟迭代子

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

聚集對象為迭代子對象提供一個寬接口,而為其他對象提供一個窄接口。換而言之,聚集對象的內(nèi)部接口應(yīng)當(dāng)對迭代子對象適當(dāng)公開,以便迭代子對象能夠?qū)奂瘜ο笥凶銐虻牧私猓瑥亩梢赃M(jìn)行迭代操作。但是,聚集對象應(yīng)當(dāng)避免向其他對象提供這些方法,因為其他對象應(yīng)當(dāng)經(jīng)過迭代子對象進(jìn)行這些工作,而不是直接操控聚集對象。

黑箱聚集于內(nèi)稟迭代子

在Java語言中,實現(xiàn)雙重接口的方法就是將迭代子類設(shè)計成聚集類的內(nèi)部成員類。這樣迭代子對象可以像聚集對象的內(nèi)部成員一樣訪問聚集對象的內(nèi)部結(jié)構(gòu)。下面給出一個示意性的實現(xiàn),說明這種雙重接口的結(jié)構(gòu)是怎樣產(chǎn)生的,以及使用雙重接口之后迭代子模式的實現(xiàn)方案。這種同時保證聚集對象的封裝和迭代子功能的實現(xiàn)方案叫做黑箱實現(xiàn)方案

由于迭代子是聚集的內(nèi)部類,迭代子可以自由訪問聚集的元素,所以迭代子可以自行實現(xiàn)迭代功能并控制對聚集元素的迭代邏輯。由于迭代子是在聚集的結(jié)構(gòu)之內(nèi)定義的,因此這樣的迭代子又叫做內(nèi)稟迭代子(Intrinsic Iterator)

為了說明黑箱方案的細(xì)節(jié),這里給出一個示意性的黑箱實現(xiàn)。在這個實現(xiàn)里,聚集類ConcreteAggregate含有一個內(nèi)部成員類ConcreteIterator,也就是實現(xiàn)了抽象迭代子接口的具體迭代子類,同時聚集并不向外界提供訪問自己內(nèi)部元素的方法。

示意性的黑箱實現(xiàn)

示例代碼

抽象聚集角色類,這個角色規(guī)定出所有的聚集必須實現(xiàn)的接口。迭代子模式要求聚集對象必須有一個工廠方法,也就是createIterator()方法,以向外界提供迭代子對象的實例。

public abstract class Aggregate {
    /**
     * 工廠方法,創(chuàng)建相應(yīng)迭代子對象的接口
     * @return
     */
    public abstract Iterator createIterator();
}

抽象迭代子角色類

public interface Iterator {
    /**
     * 迭代方法,移動至第一個元素
     */
    public void first();
    /**
     * 迭代方法,移動至下一個元素
     */
    public void next();
    /**
     * 迭代方法,是否為最后一個元素
     */
    public boolean isDone();
    /**
     * 迭代方法,返回當(dāng)前元素
     */
    public Object currentItem();
}

具體聚集角色類,實現(xiàn)了抽象聚集角色所要求的接口,也就是createIterator()方法。此外,聚集類有一個內(nèi)部成員類ConcreateIterator,這個內(nèi)部類實現(xiàn)了抽象迭代子角色所規(guī)定的接口;而工廠方法createIterator()所返還的就是這個內(nèi)部成員類的實例。

public class ConcreteAggregate extends Aggregate {
    private Object[] objArray = null;

    /**
     * 構(gòu)造方法,傳入聚合對象的具體內(nèi)容
     * 
     * @param objArray
     */
    public ConcreteAggregate(Object[] objArray) {
        this.objArray = objArray;
    }

    @Override
    public Iterator createIterator() {
        return new ConcreteIterator();
    }

    /**
     * 內(nèi)部成員類,具體迭代子類
     * @author chenshuaishuai
     *
     */
    private class ConcreteIterator implements Iterator {
        /**
         * 內(nèi)部索引,記錄當(dāng)前迭代到的索引位置
         */
        private int index = 0;
        /**
         * 記錄當(dāng)前聚集對象的大小
         */
        private int size = 0;
        /**
         * 構(gòu)造函數(shù),設(shè)置聚集對象大小和起始索引
         */
        public ConcreteIterator() {
            this.size = objArray.length;
            this.index = 0;
        }
        /**
         * 移動至第一個元素
         */
        @Override
        public void first() {
            this.index = 0;
        }

        /**
         * 迭代方法,移動到下一個元素
         */
        @Override
        public void next() {
            if (this.index < this.size) {
                this.index++;
            }
        }

        /**
         * 迭代方法,是否是最后一個元素
         */
        @Override
        public boolean isDone() {
            return (this.index >= this.size);
        }

        /**
         * 迭代方法,返還當(dāng)前位置元素
         */
        @Override
        public Object currentItem() {
            return objArray[this.index];
        }
        
    }
}

客戶端類

public class Client {
    public void operation() {
        Object[] objArray = { "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eigth" };
        Aggregate agg = new ConcreteAggregate(objArray);
        Iterator iterator = agg.createIterator();
        while (!iterator.isDone()) {
            System.out.println(iterator.currentItem());
            iterator.next();
        }
    }
    
    public static void main(String[] args) {
        Client client = new Client();
        client.operation();
    }
}

上面的例子首先創(chuàng)建了一個聚集類實例,然后調(diào)用聚集對象的工廠方法createIterator()得到一個迭代子對象。在得到迭代子的實例后,客戶端開始迭代過程,打印出所有的聚集元素。

主動迭代子和被動迭代子

主動迭代子和被動迭代子又稱作外部迭代子和內(nèi)部迭代子。

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

所謂被動(內(nèi)部)迭代子,指的是由迭代子自己來控制迭代下一個元素的步驟。因此,如果想要在迭代的過程中完成工作的話,客戶端需要把操作傳遞給迭代子,迭代子在迭代的時候會在每個元素上執(zhí)行這個操作,類似于Java的回調(diào)機(jī)制。

總體來說外部迭代器比內(nèi)部迭代器要靈活一些,因此我們常見的實現(xiàn)多屬于主動迭代子。

靜態(tài)迭代子和動態(tài)迭代子

  • 靜態(tài)迭代子由聚集對象創(chuàng)建,并持有聚集對象的一份快照(snapshot),在產(chǎn)生后這個快照的內(nèi)容就不再變化。客戶端可以繼續(xù)修改原聚集的內(nèi)容,但是迭代子對象不會反映出聚集的新變化。
    靜態(tài)迭代子的好處是它的安全性和簡易性,換而言之,靜態(tài)迭代子易于實現(xiàn),不容易出現(xiàn)錯誤。但是由于靜態(tài)迭代子將原聚集復(fù)制了一份,因此它的短處是對時間和內(nèi)存資源的消耗。
  • 動態(tài)迭代子則與靜態(tài)迭代子完全相反,在迭代子被產(chǎn)生之后,迭代子保持著對聚集元素的引用,因此,任何對于聚集元素內(nèi)容的修改都會在迭代子對象上反映出來。
    完整的動態(tài)迭代子不容易實現(xiàn),但是簡化的動態(tài)迭代子并不難實現(xiàn),大多數(shù)的Java設(shè)計師遇到的迭代子都是這種簡化的動態(tài)迭代子。為了說明什么是簡化的動態(tài)迭代子,首先需要介紹一個新的概念:Fail Fast

Fail Fast

如果一個算法開始之后,它的運算環(huán)境發(fā)生變化,是的算法無法進(jìn)行必須的調(diào)整時,這個算法就應(yīng)當(dāng)發(fā)出故障信號。這就是Fail Fast的含義。

如果聚集對象的元素在一個動態(tài)迭代子的迭代過程中發(fā)生變化是,迭代過程會受到影響而變得不能進(jìn)行。這時候,迭代子就應(yīng)當(dāng)立即拋出一個異常。這種迭代子就是實現(xiàn)了Fail Fast功能的迭代子。

Fail Fast在Java聚集中的應(yīng)用

Java語言以接口java.util.Iterator的方式支持迭代子模式,Collection接口要求提供iterator()方法,此方法在調(diào)用時返還一個Iterator類型的對象。而作為Collection接口的子類型,AbstractList的內(nèi)部成員類Itr就是實現(xiàn)了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 {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet < 0)
                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()會檢查聚集的內(nèi)容是否剛剛被外界直接修改過(不是通過迭代子提供的方法修改的)。如果在迭代開始后,聚集的內(nèi)容被外界繞過迭代子對象而世界修改的話,這個方法會立即拋出ConcurrentModificationException異常。

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

迭代子模式的優(yōu)點

  1. 迭代子模式簡化了聚集的借口。迭代子具備了一個遍歷接口,這樣聚集的接口就不必具備遍歷接口。
  2. 每一個聚集對象都可以有一個或多個迭代子對象,每一個迭代子對象的迭代狀態(tài)可以是彼此獨立的。因此,一個聚集對象可以同時有幾個迭代在進(jìn)行之中。
  3. 由于遍歷方法被封裝在迭代子角色里面,因此迭代的算法可以獨立于聚集角色變化。

參考

《JAVA與模式》之迭代子模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內(nèi)容

  • 1 場景問題# 1.1 工資表數(shù)據(jù)的整合## 考慮這樣一個實際應(yīng)用:整合工資表數(shù)據(jù)。 這個項目的背景是這樣的,項目...
    七寸知架構(gòu)閱讀 2,572評論 0 53
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,719評論 18 399
  • 設(shè)計模式匯總 一、基礎(chǔ)知識 1. 設(shè)計模式概述 定義:設(shè)計模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 3,960評論 1 15
  • 警告!警告!你2016年余額已經(jīng)不足10%!!! 進(jìn)入12月以后,很多微信公眾號文章中都出現(xiàn)了這樣的字樣。很多人在...
    藥山閱讀 1,234評論 1 51
  • 是的,如果不記下來,夢會被遺忘。 我把記事本app打開,筆電合起來擺在床頭,從而可以在第一時間噼里啪啦敲下我的夢境...
    P哥閱讀 188評論 2 0