【學(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所示:

在軟件開發(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中,List類型的對(duì)象objects用于存儲(chǔ)數(shù)據(jù),方法說明如表1所示: 表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所示:

在迭代器模式結(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所示:

(注:為了簡(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”刪除。

如下代碼片段可用于刪除聚合對(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)系:

(注:為了簡(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ì)象的基本工具之一。
- 主要優(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) 在迭代器模式中,由于引入了抽象層,增加新的聚合類和迭代器類都很方便,無須修改原有代碼,滿足“開閉原則”的要求。
- 主要缺點(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è)考慮全面的抽象迭代器并不是件很容易的事情。
- 適用場(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上做掉