在軟件構建過程當中,集合對象內部結構經常變化各異,但對於這些集合對象,咱們但願在不暴露其內部結構的同時,可讓外部客戶代碼透明地訪問其中包含的元素;同時這種「透明遍歷」也爲同一種算法在多種集合對象上進行操做提供了可能。
使用面向對象技術將這種遍歷機制抽象爲「迭代器對象」爲「應對變化中的集合對象」提供了一種優雅的方式。
迭代子(Iterator)模式又叫遊標(Cursor)模式,是對象的行爲模式。迭代子模式能夠順序地訪問一個彙集中的元素而沒必要暴漏彙集的內部表象。java
遍歷一個容器對象時算法
多個對象聚在一塊兒造成的整體稱之爲彙集(Aggregate),彙集對象是可以包容一組對象的容器對象。彙集依賴於彙集結構的抽象化,具備複雜性和多樣性,數組就是最基本的彙集,也是其餘Java彙集對象的設計基礎。
Java彙集(Collection)對象是實現了共同的java.util.Collection接口的對象,是Java語言對彙集的概念的直接支持。
彙集對象必須提供適當的方法,容許客戶端按照一個線性順序遍歷全部元素對象 ,把元素對象提取出來或者刪除掉等。一個使用匯集的系統必然會使用這些方法操做彙集對象,於是在使用聚聚的系統演化過程當中,會出現兩類狀況。
◆ 迭代邏輯沒有改變,可是須要將一種彙集對象換成另外一種彙集,由於不一樣的彙集具備不一樣的遍歷接口,因此須要修改客戶端代碼,以便將已有的迭代調用換成新彙集對象所要求的接口。
◆ 彙集不改變,可是迭代方式須要改變,好比原來只須要讀取元素和刪除元素,但如今須要增長新的;或者原來的迭代僅僅遍歷全部的元素,而如今則須要對元素加以過濾等。這時就只好修改彙集對象,修改已有的遍歷方法,或者增長新的方法。
顯然,出現這種狀況是由於所涉及的彙集設計不符合「開-閉」原則,也就是由於沒有將不變的結構從系統中抽象出來,與可變成分分割,並將可變部分的各類實現封裝起來。一個聰明的作法無疑是應當使用更加抽象的處理方法,使得在進行迭代時,客戶端根本無需知道所使用的彙集是哪一個類型;而當客戶端須要使用全新的迭代邏輯時,只須要引進一個新的迭代子對象便可,根本無需修改彙集對象自己。
迭代子模式模式即是這樣的一個抽象化的概念,這一模式之因此可以作到這一點,是由於它將迭代邏輯封裝到一個獨立的迭代子對象彙總,從而與彙集自己分隔開。迭代子對象是對遍歷的抽象化,不一樣的彙集對象能夠提供相同的迭代子對象,從而使客戶端無需知道彙集的低層結構,一個彙集能夠提供多個不一樣的迭代子對象,從而使得遍歷邏輯的變化不會影響到彙集對象自己。數組
迭代子模式有兩種實現方式,分別是白箱彙集與外稟迭代子和黑箱彙集於內稟迭代子。ide
白箱彙集與外稟迭代子
若是一個彙集的接口提供了能夠用來修改彙集元素的方法,這個接口就是所謂的寬接口。
若是彙集對象爲全部對象提供同一個接口,也就是寬接口的話,固然會知足迭代子模式對迭代子對象的要求。可是,這樣會破壞對彙集對象的封裝。這種提供寬接口的彙集叫作白箱彙集。函數
因爲彙集本身實現迭代邏輯,並向外部提供適當的接口,使得迭代子能夠從外部控制彙集元素的迭代過程。這樣一來迭代子所控制的僅僅是一個遊標而已,這種迭代子叫作遊標迭代子(Cursor Iterator)。因爲迭代子是在彙集結構以外的,所以這樣的迭代子又叫作外稟迭代子(Extrinsic Iterator)。this
實現
一個白箱彙集向外界提供訪問本身內部元素的接口(稱做遍歷方法或者Traversing Method),從而使外稟迭代子能夠經過彙集的遍歷方法實現迭代功能。
由於迭代的邏輯是由彙集對象自己提供的,因此這樣的外稟迭代子角色每每僅僅保持迭代的遊標位置。spa
角色
抽象迭代子(Iterator)角色:此抽象角色定義出遍歷元素所需的接口。
具體迭代子(ConcreteIterator)角色:此角色實現了Iterator接口,並保持迭代過程當中的遊標位置。
彙集(Aggregate)角色:此抽象角色給出建立迭代子(Iterator)對象的接口。
具體彙集(ConcreteAggregate)角色:實現了建立迭代子(Iterator)對象的接口,返回一個合適的具體迭代子實例。
客戶端(Client)角色:持有對彙集及其迭代子對象的引用,調用迭代子對象的迭代接口,也有可能經過迭代子操做彙集元素的增長和刪除。設計
抽象彙集角色類
這個角色規定出全部的具體彙集必須實現的接口。迭代子模式要求彙集對象必須有一個工廠方法,也就是createIterator()方法,以向外界提供迭代子對象的實例。code
public abstract class Aggregate { /** * 工廠方法,建立相應迭代子對象的接口 */ 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(); } }
上面的例子首先建立了一個彙集類實例,而後調用匯集對象的工廠方法createIterator()以獲得一個迭代子對象。在獲得迭代子的實例後,客戶端開始迭代過程,打印出全部的彙集元素。
意義
一個經常會問的問題是:既然白箱彙集已經向外界提供了遍歷方法,客戶端已經能夠自行進行迭代了,爲何還要應用迭代子模式,並建立一個迭代子對象進行迭代呢?
客戶端固然能夠自行進行迭代,不必定非得須要一個迭代子對象。可是,迭代子對象和迭代模式會將迭代過程抽象化,將做爲迭代消費者的客戶端與迭代負責人的迭代子責任分隔開,使得二者能夠獨立的演化。在彙集對象的種類發生變化,或者迭代的方法發生改變時,迭代子做爲一箇中介層能夠吸取變化的因素,而避免修改客戶端或者彙集自己。
此外,若是系統須要同時針對幾個不一樣的彙集對象進行迭代,而這些彙集對象所提供的遍歷方法有所不一樣時,使用迭代子模式和一個外界的迭代子對象是有意義的。具備同一迭代接口的不一樣迭代子對象處理具備不一樣遍歷接口的彙集對象,使得系統可使用一個統一的迭代接口進行全部的迭代。
黑箱彙集與內稟迭代子
若是一個彙集的接口沒有提供修改彙集元素的方法,這樣的接口就是所謂的窄接口。
彙集對象爲迭代子對象提供一個寬接口,而爲其餘對象提供一個窄接口。換言之,彙集對象的內部結構應當對迭代子對象適當公開,以便迭代子對象可以對彙集對象有足夠的瞭解,從而能夠進行迭代操做。可是,彙集對象應當避免向其餘的對象提供這些方法,由於其餘對象應當通過迭代子對象進行這些工做,而不是直接操控彙集對象。
在JAVA語言中,實現雙重接口的辦法就是將迭代子類設計成彙集類的內部成員類。這樣迭代子對象將能夠像彙集對象的內部成員同樣訪問彙集對象的內部結構。下面給出一個示意性的實現,說明這種雙重接口的結構時怎麼樣產生的,以及使用了雙重接口結構以後迭代子模式的實現方案。這種同時保證彙集對象的封裝和迭代子功能的實現的方案叫作黑箱實現方案。
因爲迭代子是彙集的內部類,迭代子能夠自由訪問彙集的元素,因此迭代子能夠自行實現迭代功能並控制對彙集元素的迭代邏輯。因爲迭代子是在彙集的結構以內定義的,所以這樣的迭代子又叫作內稟迭代子(Intrinsic Iterator)。
應用
爲了說明黑箱方案的細節,這裏給出一個示意性的黑箱實現。在這個實現裏,彙集類ConcreteAggregate含有一個內部成員類ConcreteIterator,也就是實現了抽象迭代子接口的具體迭代子類,同時彙集並不向外界提供訪問本身內部元素的方法。
抽象彙集角色類
這個角色規定出全部的具體彙集必須實現的接口。迭代子模式要求彙集對象必須有一個工廠方法,也就是createIterator()方法,以向外界提供迭代子對象的實例。
public abstract class Aggregate { /** * 工廠方法,建立相應迭代子對象的接口 */ public abstract Iterator createIterator(); }
抽象迭代子角色類
public interface Iterator { /** * 迭代方法:移動到第一個元素 */ public void first(); /** * 迭代方法:移動到下一個元素 */ public void next(); /** * 迭代方法:是否爲最後一個元素 */ public boolean isDone(); /** * 迭代方法:返還當前元素 */ public Object currentItem(); }
具體彙集角色類
實現了抽象彙集角色所要求的接口,也就是createIterator()方法。此外,彙集類有一個內部成員類ConcreteIterator,這個內部類實現了抽象迭代子角色所規定的接口;而工廠方法createIterator()所返還的就是這個內部成員類的實例。
public class ConcreteAggregate extends Aggregate { private Object[] objArray = null; /** * 構造方法,傳入聚合對象的具體內容 */ public ConcreteAggregate(Object[] objArray){ this.objArray = objArray; } @Override public Iterator createIterator() { return new ConcreteIterator(); } /** * 內部成員類,具體迭代子類 */ private class ConcreteIterator implements Iterator { //內部索引,記錄當前迭代到的索引位置 private int index = 0; //記錄當前彙集對象的大小 private int size = 0; /** * 構造函數 */ public ConcreteIterator(){ this.size = objArray.length; index = 0; } /** * 迭代方法:返還當前元素 */ @Override public Object currentItem() { return objArray[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(); } }
上面的例子首先建立了一個彙集類實例,而後調用匯集對象的工廠方法createIterator()以獲得一個迭代子對象。在獲得迭代子的實例後,客戶端開始迭代過程,打印出全部的彙集元素。
迭代子模式優勢 迭代子模式簡化了彙集的接口。迭代子具有了一個遍歷接口,這樣彙集的接口就沒必要具有遍歷接口。 每個彙集對象均可以有一個或多個迭代子對象,每個迭代子的迭代狀態能夠是彼此獨立的。所以,一個彙集對象能夠同時有幾個迭代在進行之中。 因爲遍歷算法被封裝在迭代子角色裏面,所以迭代的算法能夠獨立於彙集角色變化。