在閻宏博士的《JAVA與模式》一書中開頭是這樣描述迭代子(Iterator)模式的: 迭代子模式又叫游標(biāo)(Cursor)模式,,是對(duì)象的行為模式。迭代子模式可以順序地訪問一個(gè)聚集中的元素而不必暴露聚集的內(nèi)部表象(internal representation),。 聚集和JAVA聚集
多個(gè)對(duì)象聚在一起形成的總體稱之為聚集(Aggregate),,聚集對(duì)象是能夠包容一組對(duì)象的容器對(duì)象。聚集依賴于聚集結(jié)構(gòu)的抽象化,,具有復(fù)雜化和多樣性,。數(shù)組就是最基本的聚集,也是其他的JAVA聚集對(duì)象的設(shè)計(jì)基礎(chǔ),。 JAVA聚集對(duì)象是實(shí)現(xiàn)了共同的java.util.Collection接口的對(duì)象,,是JAVA語言對(duì)聚集概念的直接支持。從1.2版開始,,JAVA語言提供了很多種聚集,,包括Vector、ArrayList,、HashSet,、HashMap、Hashtable等,,這些都是JAVA聚集的例子,。 迭代子模式的結(jié)構(gòu)迭代子模式有兩種實(shí)現(xiàn)方式,,分別是白箱聚集與外稟迭代子和黑箱聚集于內(nèi)稟迭代子。 白箱聚集與外稟迭代子如果一個(gè)聚集的接口提供了可以用來修改聚集元素的方法,,這個(gè)接口就是所謂的寬接口,。 如果聚集對(duì)象為所有對(duì)象提供同一個(gè)接口,也就是寬接口的話,,當(dāng)然會(huì)滿足迭代子模式對(duì)迭代子對(duì)象的要求,。但是,這樣會(huì)破壞對(duì)聚集對(duì)象的封裝,。這種提供寬接口的聚集叫做白箱聚集,。聚集對(duì)象向外界提供同樣的寬接口,如下圖所示: 由于聚集自己實(shí)現(xiàn)迭代邏輯,,并向外部提供適當(dāng)?shù)慕涌?,使得迭代子可以從外部控制聚集元素的迭代過程。這樣一來迭代子所控制的僅僅是一個(gè)游標(biāo)而已,,這種迭代子叫做游標(biāo)迭代子(Cursor Iterator),。由于迭代子是在聚集結(jié)構(gòu)之外的,因此這樣的迭代子又叫做外稟迭代子(Extrinsic Iterator),。 現(xiàn)在看一看白箱聚集與外稟迭代子的實(shí)現(xiàn),。一個(gè)白箱聚集向外界提供訪問自己內(nèi)部元素的接口(稱作遍歷方法或者Traversing Method),從而使外稟迭代子可以通過聚集的遍歷方法實(shí)現(xiàn)迭代功能,。 因?yàn)榈倪壿嬍怯删奂瘜?duì)象本身提供的,,所以這樣的外稟迭代子角色往往僅僅保持迭代的游標(biāo)位置。 一個(gè)典型的由白箱聚集與外稟迭代子組成的系統(tǒng)如下圖所示,,在這個(gè)實(shí)現(xiàn)中具體迭代子角色是一個(gè)外部類,,而具體聚集角色向外界提供遍歷聚集元素的接口。
迭代子模式涉及到以下幾個(gè)角色: ● 抽象迭代子(Iterator)角色:此抽象角色定義出遍歷元素所需的接口,。 ● 具體迭代子(ConcreteIterator)角色:此角色實(shí)現(xiàn)了Iterator接口,,并保持迭代過程中的游標(biāo)位置。 ● 聚集(Aggregate)角色:此抽象角色給出創(chuàng)建迭代子(Iterator)對(duì)象的接口,。 ● 具體聚集(ConcreteAggregate)角色:實(shí)現(xiàn)了創(chuàng)建迭代子(Iterator)對(duì)象的接口,,返回一個(gè)合適的具體迭代子實(shí)例。 ● 客戶端(Client)角色:持有對(duì)聚集及其迭代子對(duì)象的引用,,調(diào)用迭代子對(duì)象的迭代接口,,也有可能通過迭代子操作聚集元素的增加和刪除。 源代碼抽象聚集角色類,,這個(gè)角色規(guī)定出所有的具體聚集必須實(shí)現(xiàn)的接口,。迭代子模式要求聚集對(duì)象必須有一個(gè)工廠方法,也就是createIterator()方法,,以向外界提供迭代子對(duì)象的實(shí)例,。 public abstract class Aggregate { /** * 工廠方法,,創(chuàng)建相應(yīng)迭代子對(duì)象的接口 */ public abstract Iterator createIterator(); } 具體聚集角色類,實(shí)現(xiàn)了抽象聚集角色類所要求的接口,,也就是createIterator()方法,。此外,還有方法getElement()向外界提供聚集元素,,而方法size()向外界提供聚集的大小等,。 public class ConcreteAggregate extends Aggregate { private Object[] objArray = null; /** * 構(gòu)造方法,傳入聚合對(duì)象的具體內(nèi)容 */ 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 { /** * 迭代方法:移動(dòng)到第一個(gè)元素 */ public void first(); /** * 迭代方法:移動(dòng)到下一個(gè)元素 */ public void next(); /** * 迭代方法:是否為最后一個(gè)元素 */ public boolean isDone(); /** * 迭代方法:返還當(dāng)前元素 */ public Object currentItem(); } 具體迭代子角色類 public class ConcreteIterator implements Iterator { //持有被迭代的具體的聚合對(duì)象 private ConcreteAggregate agg; //內(nèi)部索引,,記錄當(dāng)前迭代到的索引位置 private int index = 0; //記錄當(dāng)前聚集對(duì)象的大小 private int size = 0; public ConcreteIterator(ConcreteAggregate agg){ this.agg = agg; this.size = agg.size(); index = 0; } /** * 迭代方法:返還當(dāng)前元素 */ @Override public Object currentItem() { return agg.getElement(index); } /** * 迭代方法:移動(dòng)到第一個(gè)元素 */ @Override public void first() { index = 0; } /** * 迭代方法:是否為最后一個(gè)元素 */ @Override public boolean isDone() { return (index >= size); } /** * 迭代方法:移動(dòng)到下一個(gè)元素 */ @Override public void next() { if(index < size) { index ++; } } } 客戶端類 public class Client { public void operation(){ Object[] objArray = {"One","Two","Three","Four","Five","Six"}; //創(chuàng)建聚合對(duì)象 Aggregate agg = new ConcreteAggregate(objArray); //循環(huán)輸出聚合對(duì)象中的值 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(); } } 上面的例子首先創(chuàng)建了一個(gè)聚集類實(shí)例,,然后調(diào)用聚集對(duì)象的工廠方法createIterator()以得到一個(gè)迭代子對(duì)象。在得到迭代子的實(shí)例后,,客戶端開始迭代過程,,打印出所有的聚集元素。 外稟迭代子的意義一個(gè)常常會(huì)問的問題是:既然白箱聚集已經(jīng)向外界提供了遍歷方法,,客戶端已經(jīng)可以自行進(jìn)行迭代了,為什么還要應(yīng)用迭代子模式,,并創(chuàng)建一個(gè)迭代子對(duì)象進(jìn)行迭代呢,? 客戶端當(dāng)然可以自行進(jìn)行迭代,不一定非得需要一個(gè)迭代子對(duì)象,。但是,,迭代子對(duì)象和迭代模式會(huì)將迭代過程抽象化,將作為迭代消費(fèi)者的客戶端與迭代負(fù)責(zé)人的迭代子責(zé)任分隔開,,使得兩者可以獨(dú)立的演化,。在聚集對(duì)象的種類發(fā)生變化,或者迭代的方法發(fā)生改變時(shí),,迭代子作為一個(gè)中介層可以吸收變化的因素,,而避免修改客戶端或者聚集本身。 此外,,如果系統(tǒng)需要同時(shí)針對(duì)幾個(gè)不同的聚集對(duì)象進(jìn)行迭代,,而這些聚集對(duì)象所提供的遍歷方法有所不同時(shí),使用迭代子模式和一個(gè)外界的迭代子對(duì)象是有意義的,。具有同一迭代接口的不同迭代子對(duì)象處理具有不同遍歷接口的聚集對(duì)象,,使得系統(tǒng)可以使用一個(gè)統(tǒng)一的迭代接口進(jìn)行所有的迭代。
黑箱聚集與內(nèi)稟迭代子如果一個(gè)聚集的接口沒有提供修改聚集元素的方法,,這樣的接口就是所謂的窄接口,。 聚集對(duì)象為迭代子對(duì)象提供一個(gè)寬接口,而為其他對(duì)象提供一個(gè)窄接口,。換言之,,聚集對(duì)象的內(nèi)部結(jié)構(gòu)應(yīng)當(dāng)對(duì)迭代子對(duì)象適當(dāng)公開,,以便迭代子對(duì)象能夠?qū)奂瘜?duì)象有足夠的了解,從而可以進(jìn)行迭代操作,。但是,,聚集對(duì)象應(yīng)當(dāng)避免向其他的對(duì)象提供這些方法,因?yàn)槠渌麑?duì)象應(yīng)當(dāng)經(jīng)過迭代子對(duì)象進(jìn)行這些工作,,而不是直接操控聚集對(duì)象,。 在JAVA語言中,實(shí)現(xiàn)雙重接口的辦法就是將迭代子類設(shè)計(jì)成聚集類的內(nèi)部成員類,。這樣迭代子對(duì)象將可以像聚集對(duì)象的內(nèi)部成員一樣訪問聚集對(duì)象的內(nèi)部結(jié)構(gòu),。下面給出一個(gè)示意性的實(shí)現(xiàn),說明這種雙重接口的結(jié)構(gòu)時(shí)怎么樣產(chǎn)生的,,以及使用了雙重接口結(jié)構(gòu)之后迭代子模式的實(shí)現(xiàn)方案,。這種同時(shí)保證聚集對(duì)象的封裝和迭代子功能的實(shí)現(xiàn)的方案叫做黑箱實(shí)現(xiàn)方案。 由于迭代子是聚集的內(nèi)部類,,迭代子可以自由訪問聚集的元素,,所以迭代子可以自行實(shí)現(xiàn)迭代功能并控制對(duì)聚集元素的迭代邏輯。由于迭代子是在聚集的結(jié)構(gòu)之內(nèi)定義的,,因此這樣的迭代子又叫做內(nèi)稟迭代子(Intrinsic Iterator),。 為了說明黑箱方案的細(xì)節(jié),這里給出一個(gè)示意性的黑箱實(shí)現(xiàn),。在這個(gè)實(shí)現(xiàn)里,,聚集類ConcreteAggregate含有一個(gè)內(nèi)部成員類ConcreteIterator,也就是實(shí)現(xiàn)了抽象迭代子接口的具體迭代子類,,同時(shí)聚集并不向外界提供訪問自己內(nèi)部元素的方法,。
源代碼抽象聚集角色類,這個(gè)角色規(guī)定出所有的具體聚集必須實(shí)現(xiàn)的接口,。迭代子模式要求聚集對(duì)象必須有一個(gè)工廠方法,,也就是createIterator()方法,以向外界提供迭代子對(duì)象的實(shí)例,。 public abstract class Aggregate { /** * 工廠方法,,創(chuàng)建相應(yīng)迭代子對(duì)象的接口 */ public abstract Iterator createIterator(); } 抽象迭代子角色類 public interface Iterator { /** * 迭代方法:移動(dòng)到第一個(gè)元素 */ public void first(); /** * 迭代方法:移動(dòng)到下一個(gè)元素 */ public void next(); /** * 迭代方法:是否為最后一個(gè)元素 */ public boolean isDone(); /** * 迭代方法:返還當(dāng)前元素 */ public Object currentItem(); } 具體聚集角色類,實(shí)現(xiàn)了抽象聚集角色所要求的接口,,也就是createIterator()方法,。此外,聚集類有一個(gè)內(nèi)部成員類ConcreteIterator,,這個(gè)內(nèi)部類實(shí)現(xiàn)了抽象迭代子角色所規(guī)定的接口,;而工廠方法createIterator()所返還的就是這個(gè)內(nèi)部成員類的實(shí)例。 public class ConcreteAggregate extends Aggregate { private Object[] objArray = null; /** * 構(gòu)造方法,傳入聚合對(duì)象的具體內(nèi)容 */ public ConcreteAggregate(Object[] objArray){ this.objArray = objArray; } @Override public Iterator createIterator() { return new ConcreteIterator(); } /** * 內(nèi)部成員類,,具體迭代子類 */ private class ConcreteIterator implements Iterator { //內(nèi)部索引,,記錄當(dāng)前迭代到的索引位置 private int index = 0; //記錄當(dāng)前聚集對(duì)象的大小 private int size = 0; /** * 構(gòu)造函數(shù) */ public ConcreteIterator(){ this.size = objArray.length; index = 0; } /** * 迭代方法:返還當(dāng)前元素 */ @Override public Object currentItem() { return objArray[index]; } /** * 迭代方法:移動(dòng)到第一個(gè)元素 */ @Override public void first() { index = 0; } /** * 迭代方法:是否為最后一個(gè)元素 */ @Override public boolean isDone() { return (index >= size); } /** * 迭代方法:移動(dòng)到下一個(gè)元素 */ @Override public void next() { if(index < size) { index ++; } } } } 客戶端類 public class Client { public void operation(){ Object[] objArray = {"One","Two","Three","Four","Five","Six"}; //創(chuàng)建聚合對(duì)象 Aggregate agg = new ConcreteAggregate(objArray); //循環(huán)輸出聚合對(duì)象中的值 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(); } } 上面的例子首先創(chuàng)建了一個(gè)聚集類實(shí)例,然后調(diào)用聚集對(duì)象的工廠方法createIterator()以得到一個(gè)迭代子對(duì)象,。在得到迭代子的實(shí)例后,,客戶端開始迭代過程,打印出所有的聚集元素,。 主動(dòng)迭代子和被動(dòng)迭代子主動(dòng)迭代子和被動(dòng)迭代子又稱作外部迭代子和內(nèi)部迭代子,。 所謂主動(dòng)(外部)迭代子,指的是由客戶端來控制迭代下一個(gè)元素的步驟,,客戶端會(huì)明顯調(diào)用迭代子的next()等迭代方法,,在遍歷過程中向前進(jìn)行。 所謂被動(dòng)(內(nèi)部)迭代子,,指的是由迭代子自己來控制迭代下一個(gè)元素的步驟,。因此,如果想要在迭代的過程中完成工作的話,,客戶端就需要把操作傳遞給迭代子,,迭代子在迭代的時(shí)候會(huì)在每個(gè)元素上執(zhí)行這個(gè)操作,類似于JAVA的回調(diào)機(jī)制,。 總體來說外部迭代器比內(nèi)部迭代器要靈活一些,,因此我們常見的實(shí)現(xiàn)多屬于主動(dòng)迭代子。 靜態(tài)迭代子和動(dòng)態(tài)迭代子● 靜態(tài)迭代子由聚集對(duì)象創(chuàng)建,,并持有聚集對(duì)象的一份快照(snapshot),在產(chǎn)生后這個(gè)快照的內(nèi)容就不再變化,??蛻舳丝梢岳^續(xù)修改原聚集的內(nèi)容,但是迭代子對(duì)象不會(huì)反映出聚集的新變化,。 靜態(tài)迭代子的好處是它的安全性和簡易性,,換言之,靜態(tài)迭代子易于實(shí)現(xiàn),,不容易出現(xiàn)錯(cuò)誤,。但是由于靜態(tài)迭代子將原聚集復(fù)制了一份,因此它的短處是對(duì)時(shí)間和內(nèi)存資源的消耗,。 ● 動(dòng)態(tài)迭代子則與靜態(tài)迭代子完全相反,,在迭代子被產(chǎn)生之后,迭代子保持著對(duì)聚集元素的引用,,因此,,任何對(duì)原聚集內(nèi)容的修改都會(huì)在迭代子對(duì)象上反映出來。 完整的動(dòng)態(tài)迭代子不容易實(shí)現(xiàn),但是簡化的動(dòng)態(tài)迭代子并不難實(shí)現(xiàn),。大多數(shù)JAVA設(shè)計(jì)師遇到的迭代子都是這種簡化的動(dòng)態(tài)迭代子,。為了說明什么是簡化的動(dòng)態(tài)迭代子,首先需要介紹一個(gè)新的概念:Fail Fast,。 Fail Fast如果一個(gè)算法開始之后,,它的運(yùn)算環(huán)境發(fā)生變化,使得算法無法進(jìn)行必需的調(diào)整時(shí),,這個(gè)算法就應(yīng)當(dāng)立即發(fā)出故障信號(hào),。這就是Fail Fast的含義。 如果聚集對(duì)象的元素在一個(gè)動(dòng)態(tài)迭代子的迭代過程中發(fā)生變化時(shí),,迭代過程會(huì)受到影響而變得不能自恰,。這時(shí)候,迭代子就應(yīng)當(dāng)立即拋出一個(gè)異常,。這種迭代子就是實(shí)現(xiàn)了Fail Fast功能的迭代子,。 Fail Fast在JAVA聚集中的使用JAVA語言以接口java.util.Iterator的方式支持迭代子模式,Collection接口要求提供iterator()方法,,此方法在調(diào)用時(shí)返還一個(gè)Iterator類型的對(duì)象,。而作為Collection接口的子類型,AbstractList類的內(nèi)部成員類Itr便是實(shí)現(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 { 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()會(huì)檢查聚集的內(nèi)容是否剛剛被外界直接修改過(不是通過迭代子提供的方法修改的)。如果在迭代開始后,,聚集的內(nèi)容被外界繞過迭代子對(duì)象而直接修改的話,,這個(gè)方法會(huì)立即拋出ConcurrentModificationException()異常。 這就是說,,AbstractList.Itr迭代子是一個(gè)Fail Fast的迭代子,。 迭代子模式的優(yōu)點(diǎn)(1)迭代子模式簡化了聚集的接口,。迭代子具備了一個(gè)遍歷接口,,這樣聚集的接口就不必具備遍歷接口。 ?。?)每一個(gè)聚集對(duì)象都可以有一個(gè)或多個(gè)迭代子對(duì)象,,每一個(gè)迭代子的迭代狀態(tài)可以是彼此獨(dú)立的。因此,,一個(gè)聚集對(duì)象可以同時(shí)有幾個(gè)迭代在進(jìn)行之中,。 (3)由于遍歷算法被封裝在迭代子角色里面,,因此迭代的算法可以獨(dú)立于聚集角色變化,。
|
|