## 引入
在閻宏博士的《JAVA與模式》一書中開頭是這樣描述迭代子(Iterator)模式的:
> 迭代子模式又叫游標(Cursor)模式,是對象的行為模式。迭代子模式可以順序地訪問一個聚集中的元素而不必暴露聚集的內部表象(internal representation)。
## 定義
所謂迭代器模式就是提供一種方法順序訪問一個聚合對象中的各個元素,而不是暴露其內部的表示。在實際的開發過程中,我們可能需要針對不同的需求,可能需要以不同的方式來遍歷整個整合對象,但是我們不希望在聚合對象的抽象接口層中充斥著各種不同的便利操作。這個時候我們就需要這樣一種東西,它應該具備如下三個功能:
1、能夠便利一個聚合對象。
2、我們不需要了解聚合對象的內部結構。
3、能夠提供多種不同的遍歷方式。
這三個功能就是迭代器模式需要解決的問題。作為一個功能強大的模式,迭代器模式把在元素之間游走的責任交給迭代器,而不是聚合對象。這樣做就簡化了聚合的接口和實現,也可以讓聚合更專注在它所應該專注的事情上,這樣做就更加符合單一責任原則。
## 結構和代碼實現
### 聚集和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)角色:持有對聚集及其迭代子對象的引用,調用迭代子對象的迭代接口,也有可能通過迭代子操作聚集元素的增加和刪除。
**代碼實現**
抽象聚集角色類,這個角色規定出所有的具體聚集必須實現的接口。迭代子模式要求聚集對象必須有一個工廠方法,也就是createIterator()方法,以向外界提供迭代子對象的實例。
```
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()以得到一個迭代子對象。在得到迭代子的實例后,客戶端開始迭代過程,打印出所有的聚集元素。
### 主動迭代子和被動迭代子
主動迭代子和被動迭代子又稱作外部迭代子和內部迭代子。
所謂主動(外部)迭代子,指的是由客戶端來控制迭代下一個元素的步驟,客戶端會明顯調用迭代子的next()等迭代方法,在遍歷過程中向前進行。
所謂被動(內部)迭代子,指的是由迭代子自己來控制迭代下一個元素的步驟。因此,如果想要在迭代的過程中完成工作的話,客戶端就需要把操作傳遞給迭代子,迭代子在迭代的時候會在每個元素上執行這個操作,類似于JAVA的回調機制。
總體來說外部迭代器比內部迭代器要靈活一些,因此我們常見的實現多屬于主動迭代子。
### 靜態迭代子和動態迭代子
靜態迭代子由聚集對象創建,并持有聚集對象的一份快照(snapshot),在產生后這個快照的內容就不再變化。客戶端可以繼續修改原聚集的內容,但是迭代子對象不會反映出聚集的新變化。
靜態迭代子的好處是它的安全性和簡易性,換言之,靜態迭代子易于實現,不容易出現錯誤。但是由于靜態迭代子將原聚集復制了一份,因此它的短處是對時間和內存資源的消耗。
動態迭代子則與靜態迭代子完全相反,在迭代子被產生之后,迭代子保持著對聚集元素的引用,因此,任何對原聚集內容的修改都會在迭代子對象上反映出來。
完整的動態迭代子不容易實現,但是簡化的動態迭代子并不難實現。大多數JAVA設計師遇到的迭代子都是這種簡化的動態迭代子。為了說明什么是簡化的動態迭代子,首先需要介紹一個新的概念:Fail Fast。
### Fail Fast
如果一個算法開始之后,它的運算環境發生變化,使得算法無法進行必需的調整時,這個算法就應當立即發出故障信號。這就是Fail Fast的含義。
如果聚集對象的元素在一個動態迭代子的迭代過程中發生變化時,迭代過程會受到影響而變得不能自恰。這時候,迭代子就應當立即拋出一個異常。這種迭代子就是實現了Fail Fast功能的迭代子。
## 優點
* 1、迭代子模式簡化了聚集的接口。迭代子具備了一個遍歷接口,這樣聚集的接口就不必具備遍歷接口。
* 2、每一個聚集對象都可以有一個或多個迭代子對象,每一個迭代子的迭代狀態可以是彼此獨立的。因此,一個聚集對象可以同時有幾個迭代在進行之中。
* 3、由于遍歷算法被封裝在迭代子角色里面,因此迭代的算法可以獨立于聚集角色變化。
## 缺點
* 由于迭代器模式將存儲數據和遍歷數據的職責分離,增加新的聚合類需要對應增加新的迭代器類,類的個數成對增加,這在一定程度上增加了系統的復雜性。
## 使用場景
* 1、訪問一個聚合對象的內容而無須暴露它的內部表示。
* 2、需要為聚合對象提供多種遍歷方式。
* 3、為遍歷不同的聚合結構提供一個統一的接口。
## 總結
* 1、迭代器模式提供一種方法來訪問聚合對象,而不用暴露這個對象的內部表示。
* 2、將遍歷聚合對象中數據的行為提取出來,封裝到一個迭代器中,通過專門的迭代器來遍歷聚合對象的內部數據,這就是迭代器模式的本質。迭代器模式是“單一職責原則”的完美體現。
* 3、當使用迭代器的時候,我們依賴聚合提供遍歷。
* 4、迭代器提供了一個通用的接口,讓我們遍歷聚合的項,放我們編碼使用聚合項時,就可以使用多態機制。
- java
- 設計模式
- 設計模式總覽
- 設計原則
- 工廠方法模式
- 抽象工廠模式
- 單例模式
- 建造者模式
- 原型模式
- 適配器模式
- 裝飾者模式
- 代理模式
- 外觀模式
- 橋接模式
- 組合模式
- 享元模式
- 策略模式
- 模板方法模式
- 觀察者模式
- 迭代子模式
- 責任鏈模式
- 命令模式
- 備忘錄模式
- 狀態模式
- 訪問者模式
- 中介者模式
- 解釋器模式
- 附錄
- JVM相關
- JVM內存結構
- Java虛擬機的內存組成以及堆內存介紹
- Java堆和棧
- 附錄-數據結構的堆棧和內存分配的堆區棧區的區別
- Java內存之Java 堆
- Java內存之虛擬機和內存區域概述
- Java 內存之方法區和運行時常量池
- Java 內存之直接內存(堆外內存)
- JAVA內存模型
- Java內存模型介紹
- 內存模型如何解決緩存一致性問題
- 深入理解Java內存模型——基礎
- 深入理解Java內存模型——重排序
- 深入理解Java內存模型——順序一致性
- 深入理解Java內存模型——volatile
- 深入理解Java內存模型——鎖
- 深入理解Java內存模型——final
- 深入理解Java內存模型——總結
- 內存可見性
- JAVA對象模型
- JVM內存結構 VS Java內存模型 VS Java對象模型
- Java的對象模型
- Java的對象頭
- HotSpot虛擬機
- HotSpot虛擬機對象探秘
- 深入分析Java的編譯原理
- Java虛擬機的鎖優化技術
- 對象和數組并不是都在堆上分配內存的
- 垃圾回收
- JVM內存管理及垃圾回收
- JVM 垃圾回收器工作原理及使用實例介紹
- JVM內存回收理論與實現(對象存活的判定)
- JVM參數及調優
- CMS GC日志分析
- JVM實用參數(一)JVM類型以及編譯器模式
- JVM實用參數(二)參數分類和即時(JIT)編譯器診斷
- JVM實用參數(三)打印所有XX參數及值
- JVM實用參數(四)內存調優
- JVM實用參數(五)新生代垃圾回收
- JVM實用參數(六) 吞吐量收集器
- JVM實用參數(七)CMS收集器
- JVM實用參數(八)GC日志
- Java性能調優原則
- JVM 優化經驗總結
- 面試題整理
- 面試題1
- java日志規約
- Spring安全
- OAtuth2.0簡介
- Spring Session 簡介(一)
- Spring Session 簡介(二)
- Spring Session 簡介(三)
- Spring Security 簡介(一)
- Spring Security 簡介(二)
- Spring Security 簡介(三)
- Spring Security 簡介(四)
- Spring Security 簡介(五)
- Spring Security Oauth2 (一)
- Spring Security Oauth2 (二)
- Spring Security Oauth2 (三)
- SpringBoot
- Shiro
- Shiro和Spring Security對比
- Shiro簡介
- Session、Cookie和Cache
- Web Socket
- Spring WebFlux