行為型SEQ4 - 迭代器模式 Iterator Pattern

【學(xué)習(xí)難度:★★★☆☆,使用頻率:★★★★★】
直接出處:迭代器模式
梳理和學(xué)習(xí):https://github.com/BruceOuyang/boy-design-pattern
簡(jiǎn)書日期: 2018/03/22
簡(jiǎn)書首頁(yè):http://m.itdecent.cn/p/0fb891a7c5ed

遍歷聚合對(duì)象中的元素——迭代器模式(一)

20世紀(jì)80年代,那時(shí)我家有一臺(tái)“古老的”電視機(jī),牌子我忘了,只記得是臺(tái)黑白電視機(jī),沒有遙控器,每次開關(guān)機(jī)或者換臺(tái)都需要通過電視機(jī)上面的那些按鈕來完成,我印象最深的是那個(gè)用來?yè)Q臺(tái)的按鈕,需要親自用手去旋轉(zhuǎn)(還要使點(diǎn)勁才能擰動(dòng)),每轉(zhuǎn)一下就“啪”的響一聲,如果沒有收到任何電視頻道就會(huì)出現(xiàn)一片讓人眼花的雪花點(diǎn)。當(dāng)然,電視機(jī)上面那兩根可以前后左右移動(dòng),并能夠變長(zhǎng)變短的天線也是當(dāng)年電視機(jī)的標(biāo)志性部件之一,我記得小時(shí)候每次畫電視機(jī)時(shí)一定要畫那兩根天線,要不總覺得不是電視機(jī)。隨著科技的飛速發(fā)展,越來越高級(jí)的電視機(jī)相繼出現(xiàn),那種古老的電視機(jī)已經(jīng)很少能夠看到了。與那時(shí)的電視機(jī)相比,現(xiàn)今的電視機(jī)給我們帶來的最大便利之一就是增加了電視機(jī)遙控器,我們?cè)谶M(jìn)行開機(jī)、關(guān)機(jī)、換臺(tái)、改變音量等操作時(shí)都無須直接操作電視機(jī),可以通過遙控器來間接實(shí)現(xiàn)。我們可以將電視機(jī)看成一個(gè)存儲(chǔ)電視頻道的集合對(duì)象,通過遙控器可以對(duì)電視機(jī)中的電視頻道集合進(jìn)行操作,如返回上一個(gè)頻道、跳轉(zhuǎn)到下一個(gè)頻道或者跳轉(zhuǎn)至指定的頻道。遙控器為我們操作電視頻道帶來很大的方便,用戶并不需要知道這些頻道到底如何存儲(chǔ)在電視機(jī)中。電視機(jī)遙控器和電視機(jī)示意圖如圖1所示:

圖1 電視機(jī)遙控器與電視機(jī)示意圖

在軟件開發(fā)中,也存在大量類似電視機(jī)一樣的類,它們可以存儲(chǔ)多個(gè)成員對(duì)象(元素),這些類通常稱為聚合類(Aggregate Classes),對(duì)應(yīng)的對(duì)象稱為聚合對(duì)象。為了更加方便地操作這些聚合對(duì)象,同時(shí)可以很靈活地為聚合對(duì)象增加不同的遍歷方法,我們也需要類似電視機(jī)遙控器一樣的角色,可以訪問一個(gè)聚合對(duì)象中的元素但又不需要暴露它的內(nèi)部結(jié)構(gòu)。本章我們將要學(xué)習(xí)的迭代器模式將為聚合對(duì)象提供一個(gè)遙控器,通過引入迭代器,客戶端無須了解聚合對(duì)象的內(nèi)部結(jié)構(gòu)即可實(shí)現(xiàn)對(duì)聚合對(duì)象中成員的遍歷,還可以根據(jù)需要很方便地增加新的遍歷方式。

1 銷售管理系統(tǒng)中數(shù)據(jù)的遍歷

Sunny軟件公司為某商場(chǎng)開發(fā)了一套銷售管理系統(tǒng),在對(duì)該系統(tǒng)進(jìn)行分析和設(shè)計(jì)時(shí),Sunny軟件公司開發(fā)人員發(fā)現(xiàn)經(jīng)常需要對(duì)系統(tǒng)中的商品數(shù)據(jù)、客戶數(shù)據(jù)等進(jìn)行遍歷,為了復(fù)用這些遍歷代碼,Sunny公司開發(fā)人員設(shè)計(jì)了一個(gè)抽象的數(shù)據(jù)集合類AbstractObjectList,而將存儲(chǔ)商品和客戶等數(shù)據(jù)的類作為其子類,AbstractObjectList類結(jié)構(gòu)如圖2所示:

圖2 AbstractObjectList類結(jié)構(gòu)圖

在圖2中,List類型的對(duì)象objects用于存儲(chǔ)數(shù)據(jù),方法說明如表1所示: 表1 AbstractObjectList類方法說明

表1 AbstractObjectList類方法說明

AbstractObjectList類的子類ProductList和CustomerList分別用于存儲(chǔ)商品數(shù)據(jù)和客戶數(shù)據(jù)。

Sunny軟件公司開發(fā)人員通過對(duì)AbstractObjectList類結(jié)構(gòu)進(jìn)行分析,發(fā)現(xiàn)該設(shè)計(jì)方案存在如下幾個(gè)問題:

(1) 在圖2所示類圖中,addObject()、removeObject()等方法用于管理數(shù)據(jù),而next()、isLast()、previous()、isFirst()等方法用于遍歷數(shù)據(jù)。這將導(dǎo)致聚合類的職責(zé)過重,它既負(fù)責(zé)存儲(chǔ)和管理數(shù)據(jù),又負(fù)責(zé)遍歷數(shù)據(jù),違反了“單一職責(zé)原則”,由于聚合類非常龐大,實(shí)現(xiàn)代碼過長(zhǎng),還將給測(cè)試和維護(hù)增加難度。

(2) 如果將抽象聚合類聲明為一個(gè)接口,則在這個(gè)接口中充斥著大量方法,不利于子類實(shí)現(xiàn),違反了“接口隔離原則”。

(3) 如果將所有的遍歷操作都交給子類來實(shí)現(xiàn),將導(dǎo)致子類代碼龐大,而且必須暴露AbstractObjectList的內(nèi)部存儲(chǔ)細(xì)節(jié),向子類公開自己的私有屬性,否則子類無法實(shí)施對(duì)數(shù)據(jù)的遍歷,這將破壞AbstractObjectList類的封裝性。

如何解決上述問題?解決方案之一就是將聚合類中負(fù)責(zé)遍歷數(shù)據(jù)的方法提取出來,封裝到專門的類中,實(shí)現(xiàn)數(shù)據(jù)存儲(chǔ)和數(shù)據(jù)遍歷分離,無須暴露聚合類的內(nèi)部屬性即可對(duì)其進(jìn)行操作,而這正是迭代器模式的意圖所在。

遍歷聚合對(duì)象中的元素——迭代器模式(二)

2 迭代器模式概述

在軟件開發(fā)中,我們經(jīng)常需要使用聚合對(duì)象來存儲(chǔ)一系列數(shù)據(jù)。聚合對(duì)象擁有兩個(gè)職責(zé):一是存儲(chǔ)數(shù)據(jù);二是遍歷數(shù)據(jù)。從依賴性來看,前者是聚合對(duì)象的基本職責(zé);而后者既是可變化的,又是可分離的。因此,可以將遍歷數(shù)據(jù)的行為從聚合對(duì)象中分離出來,封裝在一個(gè)被稱之為“迭代器”的對(duì)象中,由迭代器來提供遍歷聚合對(duì)象內(nèi)部數(shù)據(jù)的行為,這將簡(jiǎn)化聚合對(duì)象的設(shè)計(jì),更符合“單一職責(zé)原則”的要求。

迭代器模式定義如下:

迭代器模式(Iterator Pattern):提供一種方法來訪問聚合對(duì)象,而不用暴露這個(gè)對(duì)象的內(nèi)部表示,其別名為游標(biāo)(Cursor)。迭代器模式是一種對(duì)象行為型模式。

在迭代器模式結(jié)構(gòu)中包含聚合和迭代器兩個(gè)層次結(jié)構(gòu),考慮到系統(tǒng)的靈活性和可擴(kuò)展性,在迭代器模式中應(yīng)用了工廠方法模式,其模式結(jié)構(gòu)如圖3所示:

圖3 迭代器模式結(jié)構(gòu)圖

在迭代器模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:

  • Iterator(抽象迭代器):它定義了訪問和遍歷元素的接口,聲明了用于遍歷數(shù)據(jù)元素的方法,例如:用于獲取第一個(gè)元素的first()方法,用于訪問下一個(gè)元素的next()方法,用于判斷是否還有下一個(gè)元素的hasNext()方法,用于獲取當(dāng)前元素的currentItem()方法等,在具體迭代器中將實(shí)現(xiàn)這些方法。

  • ConcreteIterator(具體迭代器):它實(shí)現(xiàn)了抽象迭代器接口,完成對(duì)聚合對(duì)象的遍歷,同時(shí)在具體迭代器中通過游標(biāo)來記錄在聚合對(duì)象中所處的當(dāng)前位置,在具體實(shí)現(xiàn)時(shí),游標(biāo)通常是一個(gè)表示位置的非負(fù)整數(shù)。

  • Aggregate(抽象聚合類):它用于存儲(chǔ)和管理元素對(duì)象,聲明一個(gè)createIterator()方法用于創(chuàng)建一個(gè)迭代器對(duì)象,充當(dāng)抽象迭代器工廠角色。

  • ConcreteAggregate(具體聚合類):它實(shí)現(xiàn)了在抽象聚合類中聲明的createIterator()方法,該方法返回一個(gè)與該具體聚合類對(duì)應(yīng)的具體迭代器ConcreteIterator實(shí)例。

在迭代器模式中,提供了一個(gè)外部的迭代器來對(duì)聚合對(duì)象進(jìn)行訪問和遍歷,迭代器定義了一個(gè)訪問該聚合元素的接口,并且可以跟蹤當(dāng)前遍歷的元素,了解哪些元素已經(jīng)遍歷過而哪些沒有。迭代器的引入,將使得對(duì)一個(gè)復(fù)雜聚合對(duì)象的操作變得簡(jiǎn)單。

下面我們結(jié)合代碼來對(duì)迭代器模式的結(jié)構(gòu)進(jìn)行進(jìn)一步分析。在迭代器模式中應(yīng)用了工廠方法模式,抽象迭代器對(duì)應(yīng)于抽象產(chǎn)品角色,具體迭代器對(duì)應(yīng)于具體產(chǎn)品角色,抽象聚合類對(duì)應(yīng)于抽象工廠角色,具體聚合類對(duì)應(yīng)于具體工廠角色。

在抽象迭代器中聲明了用于遍歷聚合對(duì)象中所存儲(chǔ)元素的方法,典型代碼如下所示:

interface Iterator {  
    void first(); //將游標(biāo)指向第一個(gè)元素  
    void next(); //將游標(biāo)指向下一個(gè)元素  
    boolean hasNext(); //判斷是否存在下一個(gè)元素  
    Object currentItem(); //獲取游標(biāo)指向的當(dāng)前元素  
}

在具體迭代器中將實(shí)現(xiàn)抽象迭代器聲明的遍歷數(shù)據(jù)的方法,如下代碼所示:

class ConcreteIterator implements Iterator {  
    
    //維持一個(gè)對(duì)具體聚合對(duì)象的引用,以便于訪問存儲(chǔ)在聚合對(duì)象中的數(shù)據(jù)
    private ConcreteAggregate objects; 
    
    //定義一個(gè)游標(biāo),用于記錄當(dāng)前訪問位置 
    private int cursor; 
    
    public ConcreteIterator(ConcreteAggregate objects) {  
        this.objects=objects;  
    }  

    public void first() {  ......  }  

    public void next() {  ......  }  

    public boolean hasNext() {  ......  }  

    public Object currentItem() {  ......  }  
}

需要注意的是抽象迭代器接口的設(shè)計(jì)非常重要,一方面需要充分滿足各種遍歷操作的要求,盡量為各種遍歷方法都提供聲明,另一方面又不能包含太多方法,接口中方法太多將給子類的實(shí)現(xiàn)帶來麻煩。因此,可以考慮使用抽象類來設(shè)計(jì)抽象迭代器,在抽象類中為每一個(gè)方法提供一個(gè)空的默認(rèn)實(shí)現(xiàn)。如果需要在具體迭代器中為聚合對(duì)象增加全新的遍歷操作,則必須修改抽象迭代器和具體迭代器的源代碼,這將違反“開閉原則”,因此在設(shè)計(jì)時(shí)要考慮全面,避免之后修改接口。

聚合類用于存儲(chǔ)數(shù)據(jù)并負(fù)責(zé)創(chuàng)建迭代器對(duì)象,最簡(jiǎn)單的抽象聚合類代碼如下所示:

interface Aggregate {  
    Iterator createIterator();  
}

具體聚合類作為抽象聚合類的子類,一方面負(fù)責(zé)存儲(chǔ)數(shù)據(jù),另一方面實(shí)現(xiàn)了在抽象聚合類中聲明的工廠方法createIterator(),用于返回一個(gè)與該具體聚合類對(duì)應(yīng)的具體迭代器對(duì)象,代碼如下所示:

class ConcreteAggregate implements Aggregate {    
    ......    
    public Iterator createIterator() {  
        return new ConcreteIterator(this);  
    }  
    ......  
}

思考

理解迭代器模式中具體聚合類與具體迭代器類之間存在的依賴關(guān)系和關(guān)聯(lián)關(guān)系。

遍歷聚合對(duì)象中的元素——迭代器模式(三)

3 完整解決方案

為了簡(jiǎn)化AbstractObjectList類的結(jié)構(gòu),并給不同的具體數(shù)據(jù)集合類提供不同的遍歷方式,Sunny軟件公司開發(fā)人員使用迭代器模式來重構(gòu)AbstractObjectList類的設(shè)計(jì),重構(gòu)之后的銷售管理系統(tǒng)數(shù)據(jù)遍歷結(jié)構(gòu)如圖4所示:

圖4 銷售管理系統(tǒng)數(shù)據(jù)遍歷結(jié)構(gòu)圖

(注:為了簡(jiǎn)化類圖和代碼,本結(jié)構(gòu)圖中只提供一個(gè)具體聚合類和具體迭代器類)

在圖4中,AbstractObjectList充當(dāng)抽象聚合類,ProductList充當(dāng)具體聚合類,AbstractIterator充當(dāng)抽象迭代器,ProductIterator充當(dāng)具體迭代器。完整代碼如下所示:

//在本實(shí)例中,為了詳細(xì)說明自定義迭代器的實(shí)現(xiàn)過程,我們沒有使用JDK中內(nèi)置的迭代器,事實(shí)上,JDK內(nèi)置迭代器已經(jīng)實(shí)現(xiàn)了對(duì)一個(gè)List對(duì)象的正向遍歷  
import java.util.*;  

//抽象聚合類  
abstract class AbstractObjectList {  
    protected List<Object> objects = new ArrayList<Object>();  

    public AbstractObjectList(List objects) {  
        this.objects = objects;  
    }  

    public void addObject(Object obj) {  
        this.objects.add(obj);  
    }  

    public void removeObject(Object obj) {  
        this.objects.remove(obj);  
    }  

    public List getObjects() {  
        return this.objects;  
    }  

    //聲明創(chuàng)建迭代器對(duì)象的抽象工廠方法  
    public abstract AbstractIterator createIterator();  
}  

//商品數(shù)據(jù)類:具體聚合類  
class ProductList extends AbstractObjectList {  
    public ProductList(List products) {  
        super(products);  
    }  

    //實(shí)現(xiàn)創(chuàng)建迭代器對(duì)象的具體工廠方法  
    public AbstractIterator createIterator() {  
        return new ProductIterator(this);  
    }  
}   

//抽象迭代器  
interface AbstractIterator {  
    public void next(); //移至下一個(gè)元素  
    public boolean isLast(); //判斷是否為最后一個(gè)元素  
    public void previous(); //移至上一個(gè)元素  
    public boolean isFirst(); //判斷是否為第一個(gè)元素  
    public Object getNextItem(); //獲取下一個(gè)元素  
    public Object getPreviousItem(); //獲取上一個(gè)元素  
}  

//商品迭代器:具體迭代器  
class ProductIterator implements AbstractIterator {  
    private ProductList productList;  
    private List products;  
    private int cursor1; //定義一個(gè)游標(biāo),用于記錄正向遍歷的位置  
    private int cursor2; //定義一個(gè)游標(biāo),用于記錄逆向遍歷的位置  

    public ProductIterator(ProductList list) {  
        this.productList = list;  
        this.products = list.getObjects(); //獲取集合對(duì)象  
        cursor1 = 0; //設(shè)置正向遍歷游標(biāo)的初始值  
        cursor2 = products.size() -1; //設(shè)置逆向遍歷游標(biāo)的初始值  
    }  

    public void next() {  
        if(cursor1 < products.size()) {  
            cursor1++;  
        }  
    }  

    public boolean isLast() {  
        return (cursor1 == products.size());  
    }  

    public void previous() {  
        if (cursor2 > -1) {  
            cursor2--;  
        }  
    }  

    public boolean isFirst() {  
        return (cursor2 == -1);  
    }  

    public Object getNextItem() {  
        return products.get(cursor1);  
    }   

    public Object getPreviousItem() {  
        return products.get(cursor2);  
    }     
}

編寫如下客戶端測(cè)試代碼:

class Client {  
    public static void main(String args[]) {  
        List products = new ArrayList();  
        products.add("倚天劍");  
        products.add("屠龍刀");  
        products.add("斷腸草");  
        products.add("葵花寶典");  
        products.add("四十二章經(jīng)");  

        AbstractObjectList list;  
        AbstractIterator iterator;  

        list = new ProductList(products); //創(chuàng)建聚合對(duì)象  
        iterator = list.createIterator();   //創(chuàng)建迭代器對(duì)象  

        System.out.println("正向遍歷:");      
        while(!iterator.isLast()) {  
            System.out.print(iterator.getNextItem() + ",");  
            iterator.next();  
        }  
        System.out.println();  
        System.out.println("-----------------------------");  
        System.out.println("逆向遍歷:");  
        while(!iterator.isFirst()) {  
            System.out.print(iterator.getPreviousItem() + ",");  
            iterator.previous();  
        }  
    }  
}

編譯并運(yùn)行程序,輸出結(jié)果如下:

正向遍歷:
倚天劍,屠龍刀,斷腸草,葵花寶典,四十二章經(jīng),
-----------------------------
逆向遍歷:
四十二章經(jīng),葵花寶典,斷腸草,屠龍刀,倚天劍,

如果需要增加一個(gè)新的具體聚合類,如客戶數(shù)據(jù)集合類,并且需要為客戶數(shù)據(jù)集合類提供不同于商品數(shù)據(jù)集合類的正向遍歷和逆向遍歷操作,只需增加一個(gè)新的聚合子類和一個(gè)新的具體迭代器類即可,原有類庫(kù)代碼無須修改,符合“開閉原則”;如果需要為ProductList類更換一個(gè)迭代器,只需要增加一個(gè)新的具體迭代器類作為抽象迭代器類的子類,重新實(shí)現(xiàn)遍歷方法,原有迭代器代碼無須修改,也符合“開閉原則”;但是如果要在迭代器中增加新的方法,則需要修改抽象迭代器源代碼,這將違背“開閉原則”。

遍歷聚合對(duì)象中的元素——迭代器模式(四)

4 使用內(nèi)部類實(shí)現(xiàn)迭代器

在迭代器模式結(jié)構(gòu)圖中,我們可以看到具體迭代器類和具體聚合類之間存在雙重關(guān)系,其中一個(gè)關(guān)系為關(guān)聯(lián)關(guān)系,在具體迭代器中需要維持一個(gè)對(duì)具體聚合對(duì)象的引用,該關(guān)聯(lián)關(guān)系的目的是訪問存儲(chǔ)在聚合對(duì)象中的數(shù)據(jù),以便迭代器能夠?qū)@些數(shù)據(jù)進(jìn)行遍歷操作。

除了使用關(guān)聯(lián)關(guān)系外,為了能夠讓迭代器可以訪問到聚合對(duì)象中的數(shù)據(jù),我們還可以將迭代器類設(shè)計(jì)為聚合類的內(nèi)部類,JDK中的迭代器類就是通過這種方法來實(shí)現(xiàn)的,如下AbstractList類代碼片段所示:

package java.util;  
……  
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {  
    ......  
    private class Itr implements Iterator<E> {  
        int cursor = 0;  
        ......  
}  
……  
}

我們可以通過類似的方法來設(shè)計(jì)第3節(jié)中的ProductList類,將ProductIterator類作為ProductList類的內(nèi)部類,代碼如下所示:

//商品數(shù)據(jù)類:具體聚合類  
class ProductList extends AbstractObjectList {  
    public ProductList(List products) {  
        super(products);  
    }  

    public AbstractIterator createIterator() {  
        return new ProductIterator();  
    }  

    //商品迭代器:具體迭代器,內(nèi)部類實(shí)現(xiàn)  
    private class ProductIterator implements AbstractIterator {  
        private int cursor1;  
        private int cursor2;  

        public ProductIterator() {  
            cursor1 = 0;  
            cursor2 = objects.size() -1;  
        }  

        public void next() {  
            if(cursor1 < objects.size()) {  
                cursor1++;  
            }  
        }  

        public boolean isLast() {  
            return (cursor1 == objects.size());  
        }  

        public void previous() {  
            if(cursor2 > -1) {  
                cursor2--;  
            }  
        }  

        public boolean isFirst() {  
            return (cursor2 == -1);  
        }  

        public Object getNextItem() {  
            return objects.get(cursor1);  
        }   

        public Object getPreviousItem() {  
            return objects.get(cursor2);  
        }     
    }  
}

無論使用哪種實(shí)現(xiàn)機(jī)制,客戶端代碼都是一樣的,也就是說客戶端無須關(guān)心具體迭代器對(duì)象的創(chuàng)建細(xì)節(jié),只需通過調(diào)用工廠方法createIterator()即可得到一個(gè)可用的迭代器對(duì)象,這也是使用工廠方法模式的好處,通過工廠來封裝對(duì)象的創(chuàng)建過程,簡(jiǎn)化了客戶端的調(diào)用。

遍歷聚合對(duì)象中的元素——迭代器模式(五)

5 JDK內(nèi)置迭代器

為了讓開發(fā)人員能夠更加方便地操作聚合對(duì)象,在Java、C#等編程語(yǔ)言中都提供了內(nèi)置迭代器。在Java集合框架中,常用的List和Set等聚合類都繼承(或?qū)崿F(xiàn))了java.util.Collection接口,在Collection接口中聲明了如下方法(部分):

package java.util;  

public interface Collection<E> extends Iterable<E> {  
    ……  
    boolean add(Object c);  
    boolean addAll(Collection c);  
    boolean remove(Object o);  
    boolean removeAll(Collection c);  
    boolean remainAll(Collection c);   
    Iterator iterator();  
    ……  
}

除了包含一些增加元素和刪除元素的方法外,還提供了一個(gè)iterator()方法,用于返回一個(gè)Iterator迭代器對(duì)象,以便遍歷聚合中的元素;具體的Java聚合類可以通過實(shí)現(xiàn)該iterator()方法返回一個(gè)具體的Iterator對(duì)象。

JDK中定義了抽象迭代器接口Iterator,代碼如下所示:

package java.util;  

public interface Iterator<E> {  
    boolean hasNext();  
    E next();  
    void remove();  
}

其中,hasNext()用于判斷聚合對(duì)象中是否還存在下一個(gè)元素,為了不拋出異常,在每次調(diào)用next()之前需先調(diào)用hasNext(),如果有可供訪問的元素,則返回true;next()方法用于將游標(biāo)移至下一個(gè)元素,通過它可以逐個(gè)訪問聚合中的元素,它返回游標(biāo)所越過的那個(gè)元素的引用;remove()方法用于刪除上次調(diào)用next()時(shí)所返回的元素。

Java迭代器工作原理如圖5所示,在第一個(gè)next()方法被調(diào)用時(shí),迭代器游標(biāo)由“元素1”與“元素2”之間移至“元素2”與“元素3”之間,跨越了“元素2”,因此next()方法將返回對(duì)“元素2”的引用;在第二個(gè)next()方法被調(diào)用時(shí),迭代器由“元素2”與“元素3”之間移至“元素3”和“元素4”之間,next()方法將返回對(duì)“元素3”的引用,如果此時(shí)調(diào)用remove()方法,即可將“元素3”刪除。

圖5 Java迭代器示意圖

如下代碼片段可用于刪除聚合對(duì)象中的第一個(gè)元素:

Iterator iterator = collection.iterator();   //collection是已實(shí)例化的聚合對(duì)象  
iterator.next();        // 跳過第一個(gè)元素  
iterator.remove();  // 刪除第一個(gè)元素

需要注意的是,在這里,next()方法與remove()方法的調(diào)用是相互關(guān)聯(lián)的。如果調(diào)用remove()之前,沒有先對(duì)next()進(jìn)行調(diào)用,那么將會(huì)拋出一個(gè)IllegalStateException異常,因?yàn)闆]有任何可供刪除的元素。 如下代碼片段可用于刪除兩個(gè)相鄰的元素:

iterator.remove();  
iterator.next();  //如果刪除此行代碼程序?qū)伄惓? 
iterator.remove();

在上面的代碼片段中如果將代碼iterator.next();去掉則程序運(yùn)行拋異常,因?yàn)榈诙蝿h除時(shí)將找不到可供刪除的元素。

在JDK中,Collection接口和Iterator接口充當(dāng)了迭代器模式的抽象層,分別對(duì)應(yīng)于抽象聚合類和抽象迭代器,而Collection接口的子類充當(dāng)了具體聚合類,下面以List為例加以說明,圖6列出了JDK中部分與List有關(guān)的類及它們之間的關(guān)系:

圖6 Java集合框架中部分類結(jié)構(gòu)圖

(注:為了簡(jiǎn)化類圖,本圖省略了大量方法)

在JDK中,實(shí)際情況比圖6要復(fù)雜很多,在圖6中,List接口除了繼承Collection接口的iterator()方法外,還增加了新的工廠方法listIterator(),專門用于創(chuàng)建ListIterator類型的迭代器,在List的子類LinkedList中實(shí)現(xiàn)了該方法,可用于創(chuàng)建具體的ListIterator子類ListItr的對(duì)象,代碼如下所示:

public ListIterator<E> listIterator(int index) {  
    return new ListItr(index);  
}  

listIterator()方法用于返回具體迭代器ListItr類型的對(duì)象。在JDK源碼中,AbstractList中的iterator()方法調(diào)用了listIterator()方法,如下代碼所示:

public Iterator iterator() {
    return listIterator();
}

客戶端通過調(diào)用LinkedList類的iterator()方法,即可得到一個(gè)專門用于遍歷LinkedList的迭代器對(duì)象。

大家可能會(huì)問?既然有了iterator()方法,為什么還要提供一個(gè)listIterator()方法呢?這兩個(gè)方法的功能不會(huì)存在重復(fù)嗎?干嘛要多此一舉?

這是一個(gè)好問題。我給大家簡(jiǎn)單解釋一下為什么要這樣設(shè)計(jì):由于在Iterator接口中定義的方法太少,只有三個(gè),通過這三個(gè)方法只能實(shí)現(xiàn)正向遍歷,而有時(shí)候我們需要對(duì)一個(gè)聚合對(duì)象進(jìn)行逆向遍歷等操作,因此在JDK的ListIterator接口中聲明了用于逆向遍歷的hasPrevious()和previous()等方法,如果客戶端需要調(diào)用這兩個(gè)方法來實(shí)現(xiàn)逆向遍歷,就不能再使用iterator()方法來創(chuàng)建迭代器了,因?yàn)榇藭r(shí)創(chuàng)建的迭代器對(duì)象是不具有這兩個(gè)方法的。我們只能通過如下代碼來創(chuàng)建ListIterator類型的迭代器對(duì)象:

ListIterator i = c.listIterator();

正因?yàn)槿绱?,在JDK的List接口中不得不增加對(duì)listIterator()方法的聲明,該方法可以返回一個(gè)ListIterator類型的迭代器,ListIterator迭代器具有更加強(qiáng)大的功能。

思考

為什么使用iterator()方法創(chuàng)建的迭代器無法實(shí)現(xiàn)逆向遍歷?

在Java語(yǔ)言中,我們可以直接使用JDK內(nèi)置的迭代器來遍歷聚合對(duì)象中的元素,下面的代碼演示了如何使用Java內(nèi)置的迭代器:

import java.util.*;

class IteratorDemo {
public static void process(Collection c) {
    Iterator i = c.iterator(); //創(chuàng)建迭代器對(duì)象

    //通過迭代器遍歷聚合對(duì)象  
    while(i.hasNext()) {  
        System.out.println(i.next().toString());  
    }  
}

    public static void main(String args[]) {  
        Collection persons;  
        persons = new ArrayList(); //創(chuàng)建一個(gè)ArrayList類型的聚合對(duì)象
        persons.add("張無忌");
        persons.add("小龍女");
        persons.add("令狐沖");
        persons.add("韋小寶");
        persons.add("袁紫衣");
        persons.add("小龍女");
    
        process(persons);  
    }  
}

在靜態(tài)方法process()中使用迭代器Iterator對(duì)Collection對(duì)象進(jìn)行處理,該代碼運(yùn)行結(jié)果如下:

張無忌 小龍女 令狐沖 韋小寶 袁紫衣 小龍女

如果需要更換聚合類型,如將List改成Set,則只需更換具體聚合類類名,如將上述代碼中的ArrayList改為HashSet,則輸出結(jié)果如下:

令狐沖 張無忌 韋小寶 小龍女 袁紫衣

在HashSet中合并了重復(fù)元素,并且元素以隨機(jī)次序輸出,其結(jié)果與使用ArrayList不相同。由此可見,通過使用迭代器模式,使得更換具體聚合類變得非常方便,而且還可以根據(jù)需要增加新的聚合類,新的聚合類只需要實(shí)現(xiàn)Collection接口,無須修改原有類庫(kù)代碼,符合“開閉原則”。

練習(xí)

在Sunny軟件公司開發(fā)的某教務(wù)管理系統(tǒng)中,一個(gè)班級(jí)(Class in School)包含多個(gè)學(xué)生(Student),使用Java內(nèi)置迭代器實(shí)現(xiàn)對(duì)學(xué)生信息的遍歷,要求按學(xué)生年齡由大到小的次序輸出學(xué)生信息。

遍歷聚合對(duì)象中的元素——迭代器模式(六)

6 迭代器模式總結(jié)

迭代器模式是一種使用頻率非常高的設(shè)計(jì)模式,通過引入迭代器可以將數(shù)據(jù)的遍歷功能從聚合對(duì)象中分離出來,聚合對(duì)象只負(fù)責(zé)存儲(chǔ)數(shù)據(jù),而遍歷數(shù)據(jù)由迭代器來完成。由于很多編程語(yǔ)言的類庫(kù)都已經(jīng)實(shí)現(xiàn)了迭代器模式,因此在實(shí)際開發(fā)中,我們只需要直接使用Java、C#等語(yǔ)言已定義好的迭代器即可,迭代器已經(jīng)成為我們操作聚合對(duì)象的基本工具之一。

  1. 主要優(yōu)點(diǎn)

迭代器模式的主要優(yōu)點(diǎn)如下:

(1) 它支持以不同的方式遍歷一個(gè)聚合對(duì)象,在同一個(gè)聚合對(duì)象上可以定義多種遍歷方式。在迭代器模式中只需要用一個(gè)不同的迭代器來替換原有迭代器即可改變遍歷算法,我們也可以自己定義迭代器的子類以支持新的遍歷方式。

(2) 迭代器簡(jiǎn)化了聚合類。由于引入了迭代器,在原有的聚合對(duì)象中不需要再自行提供數(shù)據(jù)遍歷等方法,這樣可以簡(jiǎn)化聚合類的設(shè)計(jì)。

(3) 在迭代器模式中,由于引入了抽象層,增加新的聚合類和迭代器類都很方便,無須修改原有代碼,滿足“開閉原則”的要求。

  1. 主要缺點(diǎn)

迭代器模式的主要缺點(diǎn)如下:

(1) 由于迭代器模式將存儲(chǔ)數(shù)據(jù)和遍歷數(shù)據(jù)的職責(zé)分離,增加新的聚合類需要對(duì)應(yīng)增加新的迭代器類,類的個(gè)數(shù)成對(duì)增加,這在一定程度上增加了系統(tǒng)的復(fù)雜性。

(2) 抽象迭代器的設(shè)計(jì)難度較大,需要充分考慮到系統(tǒng)將來的擴(kuò)展,例如JDK內(nèi)置迭代器Iterator就無法實(shí)現(xiàn)逆向遍歷,如果需要實(shí)現(xiàn)逆向遍歷,只能通過其子類ListIterator等來實(shí)現(xiàn),而ListIterator迭代器無法用于操作Set類型的聚合對(duì)象。在自定義迭代器時(shí),創(chuàng)建一個(gè)考慮全面的抽象迭代器并不是件很容易的事情。

  1. 適用場(chǎng)景

在以下情況下可以考慮使用迭代器模式:

(1) 訪問一個(gè)聚合對(duì)象的內(nèi)容而無須暴露它的內(nèi)部表示。將聚合對(duì)象的訪問與內(nèi)部數(shù)據(jù)的存儲(chǔ)分離,使得訪問聚合對(duì)象時(shí)無須了解其內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。

(2) 需要為一個(gè)聚合對(duì)象提供多種遍歷方式。

(3) 為遍歷不同的聚合結(jié)構(gòu)提供一個(gè)統(tǒng)一的接口,在該接口的實(shí)現(xiàn)類中為不同的聚合結(jié)構(gòu)提供不同的遍歷方式,而客戶端可以一致性地操作該接口。

練習(xí)

設(shè)計(jì)一個(gè)逐頁(yè)迭代器,每次可返回指定個(gè)數(shù)(一頁(yè))元素,并將該迭代器用于對(duì)數(shù)據(jù)進(jìn)行分頁(yè)處理。

練習(xí)會(huì)在我的github上做掉

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容