【學(xué)習(xí)難度:★★★☆☆,使用頻率:★★★★★】
直接出處:迭代器模式
梳理和學(xué)習(xí):https://github.com/BruceOuyang/boy-design-pattern
簡書日期: 2018/03/22
簡書首頁:http://www.lxweimin.com/p/0fb891a7c5ed
遍歷聚合對象中的元素——迭代器模式(一)
20世紀80年代,那時我家有一臺“古老的”電視機,牌子我忘了,只記得是臺黑白電視機,沒有遙控器,每次開關(guān)機或者換臺都需要通過電視機上面的那些按鈕來完成,我印象最深的是那個用來換臺的按鈕,需要親自用手去旋轉(zhuǎn)(還要使點勁才能擰動),每轉(zhuǎn)一下就“啪”的響一聲,如果沒有收到任何電視頻道就會出現(xiàn)一片讓人眼花的雪花點。當(dāng)然,電視機上面那兩根可以前后左右移動,并能夠變長變短的天線也是當(dāng)年電視機的標志性部件之一,我記得小時候每次畫電視機時一定要畫那兩根天線,要不總覺得不是電視機。隨著科技的飛速發(fā)展,越來越高級的電視機相繼出現(xiàn),那種古老的電視機已經(jīng)很少能夠看到了。與那時的電視機相比,現(xiàn)今的電視機給我們帶來的最大便利之一就是增加了電視機遙控器,我們在進行開機、關(guān)機、換臺、改變音量等操作時都無須直接操作電視機,可以通過遙控器來間接實現(xiàn)。我們可以將電視機看成一個存儲電視頻道的集合對象,通過遙控器可以對電視機中的電視頻道集合進行操作,如返回上一個頻道、跳轉(zhuǎn)到下一個頻道或者跳轉(zhuǎn)至指定的頻道。遙控器為我們操作電視頻道帶來很大的方便,用戶并不需要知道這些頻道到底如何存儲在電視機中。電視機遙控器和電視機示意圖如圖1所示:
在軟件開發(fā)中,也存在大量類似電視機一樣的類,它們可以存儲多個成員對象(元素),這些類通常稱為聚合類(Aggregate Classes),對應(yīng)的對象稱為聚合對象。為了更加方便地操作這些聚合對象,同時可以很靈活地為聚合對象增加不同的遍歷方法,我們也需要類似電視機遙控器一樣的角色,可以訪問一個聚合對象中的元素但又不需要暴露它的內(nèi)部結(jié)構(gòu)。本章我們將要學(xué)習(xí)的迭代器模式將為聚合對象提供一個遙控器,通過引入迭代器,客戶端無須了解聚合對象的內(nèi)部結(jié)構(gòu)即可實現(xiàn)對聚合對象中成員的遍歷,還可以根據(jù)需要很方便地增加新的遍歷方式。
1 銷售管理系統(tǒng)中數(shù)據(jù)的遍歷
Sunny軟件公司為某商場開發(fā)了一套銷售管理系統(tǒng),在對該系統(tǒng)進行分析和設(shè)計時,Sunny軟件公司開發(fā)人員發(fā)現(xiàn)經(jīng)常需要對系統(tǒng)中的商品數(shù)據(jù)、客戶數(shù)據(jù)等進行遍歷,為了復(fù)用這些遍歷代碼,Sunny公司開發(fā)人員設(shè)計了一個抽象的數(shù)據(jù)集合類AbstractObjectList,而將存儲商品和客戶等數(shù)據(jù)的類作為其子類,AbstractObjectList類結(jié)構(gòu)如圖2所示:
在圖2中,List類型的對象objects用于存儲數(shù)據(jù),方法說明如表1所示: 表1 AbstractObjectList類方法說明
AbstractObjectList類的子類ProductList和CustomerList分別用于存儲商品數(shù)據(jù)和客戶數(shù)據(jù)。
Sunny軟件公司開發(fā)人員通過對AbstractObjectList類結(jié)構(gòu)進行分析,發(fā)現(xiàn)該設(shè)計方案存在如下幾個問題:
(1) 在圖2所示類圖中,addObject()、removeObject()等方法用于管理數(shù)據(jù),而next()、isLast()、previous()、isFirst()等方法用于遍歷數(shù)據(jù)。這將導(dǎo)致聚合類的職責(zé)過重,它既負責(zé)存儲和管理數(shù)據(jù),又負責(zé)遍歷數(shù)據(jù),違反了“單一職責(zé)原則”,由于聚合類非常龐大,實現(xiàn)代碼過長,還將給測試和維護增加難度。
(2) 如果將抽象聚合類聲明為一個接口,則在這個接口中充斥著大量方法,不利于子類實現(xiàn),違反了“接口隔離原則”。
(3) 如果將所有的遍歷操作都交給子類來實現(xiàn),將導(dǎo)致子類代碼龐大,而且必須暴露AbstractObjectList的內(nèi)部存儲細節(jié),向子類公開自己的私有屬性,否則子類無法實施對數(shù)據(jù)的遍歷,這將破壞AbstractObjectList類的封裝性。
如何解決上述問題?解決方案之一就是將聚合類中負責(zé)遍歷數(shù)據(jù)的方法提取出來,封裝到專門的類中,實現(xiàn)數(shù)據(jù)存儲和數(shù)據(jù)遍歷分離,無須暴露聚合類的內(nèi)部屬性即可對其進行操作,而這正是迭代器模式的意圖所在。
遍歷聚合對象中的元素——迭代器模式(二)
2 迭代器模式概述
在軟件開發(fā)中,我們經(jīng)常需要使用聚合對象來存儲一系列數(shù)據(jù)。聚合對象擁有兩個職責(zé):一是存儲數(shù)據(jù);二是遍歷數(shù)據(jù)。從依賴性來看,前者是聚合對象的基本職責(zé);而后者既是可變化的,又是可分離的。因此,可以將遍歷數(shù)據(jù)的行為從聚合對象中分離出來,封裝在一個被稱之為“迭代器”的對象中,由迭代器來提供遍歷聚合對象內(nèi)部數(shù)據(jù)的行為,這將簡化聚合對象的設(shè)計,更符合“單一職責(zé)原則”的要求。
迭代器模式定義如下:
迭代器模式(Iterator Pattern):提供一種方法來訪問聚合對象,而不用暴露這個對象的內(nèi)部表示,其別名為游標(Cursor)。迭代器模式是一種對象行為型模式。
在迭代器模式結(jié)構(gòu)中包含聚合和迭代器兩個層次結(jié)構(gòu),考慮到系統(tǒng)的靈活性和可擴展性,在迭代器模式中應(yīng)用了工廠方法模式,其模式結(jié)構(gòu)如圖3所示:
在迭代器模式結(jié)構(gòu)圖中包含如下幾個角色:
Iterator(抽象迭代器):它定義了訪問和遍歷元素的接口,聲明了用于遍歷數(shù)據(jù)元素的方法,例如:用于獲取第一個元素的first()方法,用于訪問下一個元素的next()方法,用于判斷是否還有下一個元素的hasNext()方法,用于獲取當(dāng)前元素的currentItem()方法等,在具體迭代器中將實現(xiàn)這些方法。
ConcreteIterator(具體迭代器):它實現(xiàn)了抽象迭代器接口,完成對聚合對象的遍歷,同時在具體迭代器中通過游標來記錄在聚合對象中所處的當(dāng)前位置,在具體實現(xiàn)時,游標通常是一個表示位置的非負整數(shù)。
Aggregate(抽象聚合類):它用于存儲和管理元素對象,聲明一個createIterator()方法用于創(chuàng)建一個迭代器對象,充當(dāng)抽象迭代器工廠角色。
ConcreteAggregate(具體聚合類):它實現(xiàn)了在抽象聚合類中聲明的createIterator()方法,該方法返回一個與該具體聚合類對應(yīng)的具體迭代器ConcreteIterator實例。
在迭代器模式中,提供了一個外部的迭代器來對聚合對象進行訪問和遍歷,迭代器定義了一個訪問該聚合元素的接口,并且可以跟蹤當(dāng)前遍歷的元素,了解哪些元素已經(jīng)遍歷過而哪些沒有。迭代器的引入,將使得對一個復(fù)雜聚合對象的操作變得簡單。
下面我們結(jié)合代碼來對迭代器模式的結(jié)構(gòu)進行進一步分析。在迭代器模式中應(yīng)用了工廠方法模式,抽象迭代器對應(yīng)于抽象產(chǎn)品角色,具體迭代器對應(yīng)于具體產(chǎn)品角色,抽象聚合類對應(yīng)于抽象工廠角色,具體聚合類對應(yīng)于具體工廠角色。
在抽象迭代器中聲明了用于遍歷聚合對象中所存儲元素的方法,典型代碼如下所示:
interface Iterator {
void first(); //將游標指向第一個元素
void next(); //將游標指向下一個元素
boolean hasNext(); //判斷是否存在下一個元素
Object currentItem(); //獲取游標指向的當(dāng)前元素
}
在具體迭代器中將實現(xiàn)抽象迭代器聲明的遍歷數(shù)據(jù)的方法,如下代碼所示:
class ConcreteIterator implements Iterator {
//維持一個對具體聚合對象的引用,以便于訪問存儲在聚合對象中的數(shù)據(jù)
private ConcreteAggregate objects;
//定義一個游標,用于記錄當(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è)計非常重要,一方面需要充分滿足各種遍歷操作的要求,盡量為各種遍歷方法都提供聲明,另一方面又不能包含太多方法,接口中方法太多將給子類的實現(xiàn)帶來麻煩。因此,可以考慮使用抽象類來設(shè)計抽象迭代器,在抽象類中為每一個方法提供一個空的默認實現(xiàn)。如果需要在具體迭代器中為聚合對象增加全新的遍歷操作,則必須修改抽象迭代器和具體迭代器的源代碼,這將違反“開閉原則”,因此在設(shè)計時要考慮全面,避免之后修改接口。
聚合類用于存儲數(shù)據(jù)并負責(zé)創(chuàng)建迭代器對象,最簡單的抽象聚合類代碼如下所示:
interface Aggregate {
Iterator createIterator();
}
具體聚合類作為抽象聚合類的子類,一方面負責(zé)存儲數(shù)據(jù),另一方面實現(xiàn)了在抽象聚合類中聲明的工廠方法createIterator(),用于返回一個與該具體聚合類對應(yīng)的具體迭代器對象,代碼如下所示:
class ConcreteAggregate implements Aggregate {
......
public Iterator createIterator() {
return new ConcreteIterator(this);
}
......
}
思考
理解迭代器模式中具體聚合類與具體迭代器類之間存在的依賴關(guān)系和關(guān)聯(lián)關(guān)系。
遍歷聚合對象中的元素——迭代器模式(三)
3 完整解決方案
為了簡化AbstractObjectList類的結(jié)構(gòu),并給不同的具體數(shù)據(jù)集合類提供不同的遍歷方式,Sunny軟件公司開發(fā)人員使用迭代器模式來重構(gòu)AbstractObjectList類的設(shè)計,重構(gòu)之后的銷售管理系統(tǒng)數(shù)據(jù)遍歷結(jié)構(gòu)如圖4所示:
(注:為了簡化類圖和代碼,本結(jié)構(gòu)圖中只提供一個具體聚合類和具體迭代器類)
在圖4中,AbstractObjectList充當(dāng)抽象聚合類,ProductList充當(dāng)具體聚合類,AbstractIterator充當(dāng)抽象迭代器,ProductIterator充當(dāng)具體迭代器。完整代碼如下所示:
//在本實例中,為了詳細說明自定義迭代器的實現(xiàn)過程,我們沒有使用JDK中內(nèi)置的迭代器,事實上,JDK內(nèi)置迭代器已經(jīng)實現(xiàn)了對一個List對象的正向遍歷
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)建迭代器對象的抽象工廠方法
public abstract AbstractIterator createIterator();
}
//商品數(shù)據(jù)類:具體聚合類
class ProductList extends AbstractObjectList {
public ProductList(List products) {
super(products);
}
//實現(xiàn)創(chuàng)建迭代器對象的具體工廠方法
public AbstractIterator createIterator() {
return new ProductIterator(this);
}
}
//抽象迭代器
interface AbstractIterator {
public void next(); //移至下一個元素
public boolean isLast(); //判斷是否為最后一個元素
public void previous(); //移至上一個元素
public boolean isFirst(); //判斷是否為第一個元素
public Object getNextItem(); //獲取下一個元素
public Object getPreviousItem(); //獲取上一個元素
}
//商品迭代器:具體迭代器
class ProductIterator implements AbstractIterator {
private ProductList productList;
private List products;
private int cursor1; //定義一個游標,用于記錄正向遍歷的位置
private int cursor2; //定義一個游標,用于記錄逆向遍歷的位置
public ProductIterator(ProductList list) {
this.productList = list;
this.products = list.getObjects(); //獲取集合對象
cursor1 = 0; //設(shè)置正向遍歷游標的初始值
cursor2 = products.size() -1; //設(shè)置逆向遍歷游標的初始值
}
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);
}
}
編寫如下客戶端測試代碼:
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)建聚合對象
iterator = list.createIterator(); //創(chuàng)建迭代器對象
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();
}
}
}
編譯并運行程序,輸出結(jié)果如下:
正向遍歷:
倚天劍,屠龍刀,斷腸草,葵花寶典,四十二章經(jīng),
-----------------------------
逆向遍歷:
四十二章經(jīng),葵花寶典,斷腸草,屠龍刀,倚天劍,
如果需要增加一個新的具體聚合類,如客戶數(shù)據(jù)集合類,并且需要為客戶數(shù)據(jù)集合類提供不同于商品數(shù)據(jù)集合類的正向遍歷和逆向遍歷操作,只需增加一個新的聚合子類和一個新的具體迭代器類即可,原有類庫代碼無須修改,符合“開閉原則”;如果需要為ProductList類更換一個迭代器,只需要增加一個新的具體迭代器類作為抽象迭代器類的子類,重新實現(xiàn)遍歷方法,原有迭代器代碼無須修改,也符合“開閉原則”;但是如果要在迭代器中增加新的方法,則需要修改抽象迭代器源代碼,這將違背“開閉原則”。
遍歷聚合對象中的元素——迭代器模式(四)
4 使用內(nèi)部類實現(xiàn)迭代器
在迭代器模式結(jié)構(gòu)圖中,我們可以看到具體迭代器類和具體聚合類之間存在雙重關(guān)系,其中一個關(guān)系為關(guān)聯(lián)關(guān)系,在具體迭代器中需要維持一個對具體聚合對象的引用,該關(guān)聯(lián)關(guān)系的目的是訪問存儲在聚合對象中的數(shù)據(jù),以便迭代器能夠?qū)@些數(shù)據(jù)進行遍歷操作。
除了使用關(guān)聯(lián)關(guān)系外,為了能夠讓迭代器可以訪問到聚合對象中的數(shù)據(jù),我們還可以將迭代器類設(shè)計為聚合類的內(nèi)部類,JDK中的迭代器類就是通過這種方法來實現(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è)計第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)部類實現(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);
}
}
}
無論使用哪種實現(xiàn)機制,客戶端代碼都是一樣的,也就是說客戶端無須關(guān)心具體迭代器對象的創(chuàng)建細節(jié),只需通過調(diào)用工廠方法createIterator()即可得到一個可用的迭代器對象,這也是使用工廠方法模式的好處,通過工廠來封裝對象的創(chuàng)建過程,簡化了客戶端的調(diào)用。
遍歷聚合對象中的元素——迭代器模式(五)
5 JDK內(nèi)置迭代器
為了讓開發(fā)人員能夠更加方便地操作聚合對象,在Java、C#等編程語言中都提供了內(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();
……
}
除了包含一些增加元素和刪除元素的方法外,還提供了一個iterator()方法,用于返回一個Iterator迭代器對象,以便遍歷聚合中的元素;具體的Java聚合類可以通過實現(xiàn)該iterator()方法返回一個具體的Iterator對象。
JDK中定義了抽象迭代器接口Iterator,代碼如下所示:
package java.util;
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
其中,hasNext()用于判斷聚合對象中是否還存在下一個元素,為了不拋出異常,在每次調(diào)用next()之前需先調(diào)用hasNext(),如果有可供訪問的元素,則返回true;next()方法用于將游標移至下一個元素,通過它可以逐個訪問聚合中的元素,它返回游標所越過的那個元素的引用;remove()方法用于刪除上次調(diào)用next()時所返回的元素。
Java迭代器工作原理如圖5所示,在第一個next()方法被調(diào)用時,迭代器游標由“元素1”與“元素2”之間移至“元素2”與“元素3”之間,跨越了“元素2”,因此next()方法將返回對“元素2”的引用;在第二個next()方法被調(diào)用時,迭代器由“元素2”與“元素3”之間移至“元素3”和“元素4”之間,next()方法將返回對“元素3”的引用,如果此時調(diào)用remove()方法,即可將“元素3”刪除。
如下代碼片段可用于刪除聚合對象中的第一個元素:
Iterator iterator = collection.iterator(); //collection是已實例化的聚合對象
iterator.next(); // 跳過第一個元素
iterator.remove(); // 刪除第一個元素
需要注意的是,在這里,next()方法與remove()方法的調(diào)用是相互關(guān)聯(lián)的。如果調(diào)用remove()之前,沒有先對next()進行調(diào)用,那么將會拋出一個IllegalStateException異常,因為沒有任何可供刪除的元素。 如下代碼片段可用于刪除兩個相鄰的元素:
iterator.remove();
iterator.next(); //如果刪除此行代碼程序?qū)伄惓?
iterator.remove();
在上面的代碼片段中如果將代碼iterator.next();去掉則程序運行拋異常,因為第二次刪除時將找不到可供刪除的元素。
在JDK中,Collection接口和Iterator接口充當(dāng)了迭代器模式的抽象層,分別對應(yīng)于抽象聚合類和抽象迭代器,而Collection接口的子類充當(dāng)了具體聚合類,下面以List為例加以說明,圖6列出了JDK中部分與List有關(guān)的類及它們之間的關(guān)系:
(注:為了簡化類圖,本圖省略了大量方法)
在JDK中,實際情況比圖6要復(fù)雜很多,在圖6中,List接口除了繼承Collection接口的iterator()方法外,還增加了新的工廠方法listIterator(),專門用于創(chuàng)建ListIterator類型的迭代器,在List的子類LinkedList中實現(xiàn)了該方法,可用于創(chuàng)建具體的ListIterator子類ListItr的對象,代碼如下所示:
public ListIterator<E> listIterator(int index) {
return new ListItr(index);
}
listIterator()方法用于返回具體迭代器ListItr類型的對象。在JDK源碼中,AbstractList中的iterator()方法調(diào)用了listIterator()方法,如下代碼所示:
public Iterator iterator() {
return listIterator();
}
客戶端通過調(diào)用LinkedList類的iterator()方法,即可得到一個專門用于遍歷LinkedList的迭代器對象。
大家可能會問?既然有了iterator()方法,為什么還要提供一個listIterator()方法呢?這兩個方法的功能不會存在重復(fù)嗎?干嘛要多此一舉?
這是一個好問題。我給大家簡單解釋一下為什么要這樣設(shè)計:由于在Iterator接口中定義的方法太少,只有三個,通過這三個方法只能實現(xiàn)正向遍歷,而有時候我們需要對一個聚合對象進行逆向遍歷等操作,因此在JDK的ListIterator接口中聲明了用于逆向遍歷的hasPrevious()和previous()等方法,如果客戶端需要調(diào)用這兩個方法來實現(xiàn)逆向遍歷,就不能再使用iterator()方法來創(chuàng)建迭代器了,因為此時創(chuàng)建的迭代器對象是不具有這兩個方法的。我們只能通過如下代碼來創(chuàng)建ListIterator類型的迭代器對象:
ListIterator i = c.listIterator();
正因為如此,在JDK的List接口中不得不增加對listIterator()方法的聲明,該方法可以返回一個ListIterator類型的迭代器,ListIterator迭代器具有更加強大的功能。
思考
為什么使用iterator()方法創(chuàng)建的迭代器無法實現(xiàn)逆向遍歷?
在Java語言中,我們可以直接使用JDK內(nèi)置的迭代器來遍歷聚合對象中的元素,下面的代碼演示了如何使用Java內(nèi)置的迭代器:
import java.util.*;
class IteratorDemo {
public static void process(Collection c) {
Iterator i = c.iterator(); //創(chuàng)建迭代器對象
//通過迭代器遍歷聚合對象
while(i.hasNext()) {
System.out.println(i.next().toString());
}
}
public static void main(String args[]) {
Collection persons;
persons = new ArrayList(); //創(chuàng)建一個ArrayList類型的聚合對象
persons.add("張無忌");
persons.add("小龍女");
persons.add("令狐沖");
persons.add("韋小寶");
persons.add("袁紫衣");
persons.add("小龍女");
process(persons);
}
}
在靜態(tài)方法process()中使用迭代器Iterator對Collection對象進行處理,該代碼運行結(jié)果如下:
張無忌 小龍女 令狐沖 韋小寶 袁紫衣 小龍女
如果需要更換聚合類型,如將List改成Set,則只需更換具體聚合類類名,如將上述代碼中的ArrayList改為HashSet,則輸出結(jié)果如下:
令狐沖 張無忌 韋小寶 小龍女 袁紫衣
在HashSet中合并了重復(fù)元素,并且元素以隨機次序輸出,其結(jié)果與使用ArrayList不相同。由此可見,通過使用迭代器模式,使得更換具體聚合類變得非常方便,而且還可以根據(jù)需要增加新的聚合類,新的聚合類只需要實現(xiàn)Collection接口,無須修改原有類庫代碼,符合“開閉原則”。
練習(xí)
在Sunny軟件公司開發(fā)的某教務(wù)管理系統(tǒng)中,一個班級(Class in School)包含多個學(xué)生(Student),使用Java內(nèi)置迭代器實現(xiàn)對學(xué)生信息的遍歷,要求按學(xué)生年齡由大到小的次序輸出學(xué)生信息。
遍歷聚合對象中的元素——迭代器模式(六)
6 迭代器模式總結(jié)
迭代器模式是一種使用頻率非常高的設(shè)計模式,通過引入迭代器可以將數(shù)據(jù)的遍歷功能從聚合對象中分離出來,聚合對象只負責(zé)存儲數(shù)據(jù),而遍歷數(shù)據(jù)由迭代器來完成。由于很多編程語言的類庫都已經(jīng)實現(xiàn)了迭代器模式,因此在實際開發(fā)中,我們只需要直接使用Java、C#等語言已定義好的迭代器即可,迭代器已經(jīng)成為我們操作聚合對象的基本工具之一。
- 主要優(yōu)點
迭代器模式的主要優(yōu)點如下:
(1) 它支持以不同的方式遍歷一個聚合對象,在同一個聚合對象上可以定義多種遍歷方式。在迭代器模式中只需要用一個不同的迭代器來替換原有迭代器即可改變遍歷算法,我們也可以自己定義迭代器的子類以支持新的遍歷方式。
(2) 迭代器簡化了聚合類。由于引入了迭代器,在原有的聚合對象中不需要再自行提供數(shù)據(jù)遍歷等方法,這樣可以簡化聚合類的設(shè)計。
(3) 在迭代器模式中,由于引入了抽象層,增加新的聚合類和迭代器類都很方便,無須修改原有代碼,滿足“開閉原則”的要求。
- 主要缺點
迭代器模式的主要缺點如下:
(1) 由于迭代器模式將存儲數(shù)據(jù)和遍歷數(shù)據(jù)的職責(zé)分離,增加新的聚合類需要對應(yīng)增加新的迭代器類,類的個數(shù)成對增加,這在一定程度上增加了系統(tǒng)的復(fù)雜性。
(2) 抽象迭代器的設(shè)計難度較大,需要充分考慮到系統(tǒng)將來的擴展,例如JDK內(nèi)置迭代器Iterator就無法實現(xiàn)逆向遍歷,如果需要實現(xiàn)逆向遍歷,只能通過其子類ListIterator等來實現(xiàn),而ListIterator迭代器無法用于操作Set類型的聚合對象。在自定義迭代器時,創(chuàng)建一個考慮全面的抽象迭代器并不是件很容易的事情。
- 適用場景
在以下情況下可以考慮使用迭代器模式:
(1) 訪問一個聚合對象的內(nèi)容而無須暴露它的內(nèi)部表示。將聚合對象的訪問與內(nèi)部數(shù)據(jù)的存儲分離,使得訪問聚合對象時無須了解其內(nèi)部實現(xiàn)細節(jié)。
(2) 需要為一個聚合對象提供多種遍歷方式。
(3) 為遍歷不同的聚合結(jié)構(gòu)提供一個統(tǒng)一的接口,在該接口的實現(xiàn)類中為不同的聚合結(jié)構(gòu)提供不同的遍歷方式,而客戶端可以一致性地操作該接口。
練習(xí)
設(shè)計一個逐頁迭代器,每次可返回指定個數(shù)(一頁)元素,并將該迭代器用于對數(shù)據(jù)進行分頁處理。
練習(xí)會在我的github上做掉