文章目錄
在閻宏博士的《JAVA與模式》一書中開頭是這樣描述迭代子(Iterator)模式的:
迭代子模式又叫遊標(Cursor)模式,是對象的行為模式。迭代子模式可以順序地訪問一個聚集中的元素而不必暴露聚集的內部表象(internal representation)。
聚集和JAVA聚集
多個對象聚在一起形成的總體稱之為聚集(Aggregate),聚集對象是能夠包容一組對象的容器物件。聚集依賴於聚集結構的抽象化,具有複雜化和多樣性。數組就是最基本的聚集,也是其他的JAVA聚集對象的設計基礎。
JAVA聚集對象是實現了共同的java.util.Collection介面的對象,是JAVA語言對聚集概念的直接支援。從1.2版開始,JAVA語言提供了很多種聚集,包括Vector、ArrayList、HashSet、HashMap、Hashtable等,這些都是JAVA聚集的例子。
迭代子模式的結構
迭代子模式有兩種實現方式,分別是白箱聚集與外稟迭代子和黑箱聚集於內稟迭代子。
白箱聚集與外稟迭代子
如果一個聚集的介面提供了可以用來修改聚集元素的方法,這個介面就是所謂的寬介面。
如果聚集對象為所有對象提供同一個介面,也就是寬介面的話,當然會滿足迭代子模式對迭代子物件的要求。但是,這樣會破壞對聚集對象的封裝。這種提供寬介面的聚集叫做白箱聚集。聚集對象向外界提供同樣的寬介面,如所示:
由於聚集自己實現迭代邏輯,並向外部提供適當的介面,使得迭代子可以從外部控制聚集元素的迭代過程。這樣一來迭代子所控制的僅僅是一個遊標而已,這種迭代子叫做遊標迭代子(Cursor Iterator)。由於迭代子是在聚集結構之外的,因此這樣的迭代子又叫做外稟迭代子(Extrinsic Iterator)。
現在看一看白箱聚集與外稟迭代子的實現。一個白箱聚集向外界提供訪問自己內部元素的介面(稱作遍曆方法或者Traversing Method),從而使外稟迭代子可以通過聚集的遍曆方法實現迭代功能。
因為迭代的邏輯是由聚集對象本身提供的,所以這樣的外稟迭代子角色往往僅僅保持迭代的遊標位置。
一個典型的由白箱聚集與外稟迭代子組成的系統如所示,在這個實現中具體迭代子角色是一個外部類,而具體聚集角色向外界提供遍曆聚集元素的介面。
迭代子模式涉及到以下幾個角色:
● 抽象迭代子(Iterator)角色:此抽象角色定義出遍曆元素所需的介面。
● 具體迭代子(ConcreteIterator)角色:此角色實現了Iterator介面,並保持迭代過程中的遊標位置。
● 聚集(Aggregate)角色:此抽象角色給出建立迭代子(Iterator)對象的介面。
● 具體聚集(ConcreteAggregate)角色:實現了建立迭代子(Iterator)對象的介面,返回一個合適的具體迭代子執行個體。
● 用戶端(Client)角色:持有對聚集及其迭代子物件的引用,調用迭代子物件的迭代介面,也有可能通過迭代子操作聚集元素的增加和刪除。
原始碼
抽象聚集角色類,這個角色規定出所有的具體聚集必須實現的介面。迭代子模式要求聚集對象必須有一個Factory 方法,也就是createIterator()方法,以向外界提供迭代子物件的執行個體。
public abstract class Aggregate { /** * Factory 方法,建立相應迭代子物件的介面 */ public abstract Iterator createIterator();}
具體聚集角色類,實現了抽象聚集角色類所要求的介面,也就是createIterator()方法。此外,還有方法getElement()向外界提供聚集元素,而方法size()向外界提供聚集的大小等。
public class ConcreteAggregate extends Aggregate { private Object[] objArray = null; /** * 構造方法,傳入彙總對象的具體內容 */ 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 { /** * 迭代方法:移動到第一個元素 */ public void first(); /** * 迭代方法:移動到下一個元素 */ public void next(); /** * 迭代方法:是否為最後一個元素 */ public boolean isDone(); /** * 迭代方法:返還當前元素 */ public Object currentItem();}
具體迭代子角色類
public class ConcreteIterator implements Iterator { //持有被迭代的具體的彙總對象 private ConcreteAggregate agg; //內部索引,記錄當前迭代到的索引位置 private int index = 0; //記錄當前聚集對象的大小 private int size = 0; public ConcreteIterator(ConcreteAggregate agg){ this.agg = agg; this.size = agg.size(); index = 0; } /** * 迭代方法:返還當前元素 */ @Override public Object currentItem() { return agg.getElement(index); } /** * 迭代方法:移動到第一個元素 */ @Override public void first() { index = 0; } /** * 迭代方法:是否為最後一個元素 */ @Override public boolean isDone() { return (index >= size); } /** * 迭代方法:移動到下一個元素 */ @Override public void next() { if(index < size) { index ++; } }}
用戶端類
public class Client { public void operation(){ Object[] objArray = {"One","Two","Three","Four","Five","Six"}; //建立彙總對象 Aggregate agg = new ConcreteAggregate(objArray); //迴圈輸出彙總對象中的值 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(); }}
上面的例子首先建立了一個聚集類執行個體,然後調用聚集對象的Factory 方法createIterator()以得到一個迭代子物件。在得到迭代子的執行個體後,用戶端開始迭代過程,列印出所有的聚集元素。
外稟迭代子的意義
一個常常會問的問題是:既然白箱聚集已經向外界提供了遍曆方法,用戶端已經可以自行進行迭代了,為什麼還要應用迭代子模式,並建立一個迭代子物件進行迭代呢?
用戶端當然可以自行進行迭代,不一定非得需要一個迭代子物件。但是,迭代子物件和迭代模式會將迭代過程抽象化,將作為迭代消費者的用戶端與迭代負責人的迭代子責任分隔開,使得兩者可以獨立的演化。在聚集對象的種類發生變化,或者迭代的方法發生改變時,迭代子作為一個中介層可以吸收變化的因素,而避免修改用戶端或者聚集本身。
此外,如果系統需要同時針對幾個不同的聚集對象進行迭代,而這些聚集對象所提供的遍曆方法有所不同時,使用迭代子模式和一個外界的迭代子物件是有意義的。具有同一迭代介面的不同迭代子物件處理具有不同遍曆介面的聚集對象,使得系統可以使用一個統一的迭代介面進行所有的迭代。
黑箱聚集與內稟迭代子
如果一個聚集的介面沒有提供修改聚集元素的方法,這樣的介面就是所謂的窄介面。
聚集對象為迭代子物件提供一個寬介面,而為其他對象提供一個窄介面。換言之,聚集對象的內部結構應當對迭代子物件適當公開,以便迭代子物件能夠對聚集對象有足夠的瞭解,從而可以進行迭代操作。但是,聚集對象應當避免向其他的對象提供這些方法,因為其他對象應當經過迭代子物件進行這些工作,而不是直接操控聚集對象。