迭代器模式是一種使用頻率非常高的設計模式,迭代器用於對一個聚合物件進行遍歷。通過引入迭代器可以將資料的遍歷功能從聚合物件中分離出來,聚合物件只負責儲存資料,聚合物件只負責儲存資料,而遍歷資料由迭代器來完成。
模式動機
一個聚合物件,如一個列表(List)或者一個集合(Set),應該提供一種方法來讓別人可以訪問它的元素,而又不需要暴露它的內部結構。此外,針對不同的需要,可能還要以不同方式遍歷整個聚合物件,但是我們不希在聚合物件的抽象層介面中充斥著各種不同遍歷的操作。怎樣遍歷一個聚合物件,又不需要了解聚合物件的內部結構,還能提供多種不同的遍歷方式,這就是迭代器模式所要解決的問題。
迭代器模式中,提供一個外部的迭代器來對聚合物件進行訪問和遍歷,迭代器定義一個訪問該聚合元素的介面,並且可以跟蹤當前遍歷物件,瞭解哪些元素已經遍歷過而哪些沒有。
模式定義
提供一種方法來訪問聚合物件,而不用暴露這個物件的內部表示,其別名為遊標(Cursor)。迭代器模式是一種物件行為模式。
模式結構
-
Iterator(抽象迭代器)
抽象迭代器定義了訪問和遍歷元素的介面,一般宣告以下方法:
- 用於獲取第一個元素的 first()
- 用於訪問下一個元素的 next()
- 用於判斷是否還有下一個元素的 hasNext()
- 用於獲取當前元素的 currentItem()
-
ConcreteIterator(具體迭代器)
具體迭代器實現了抽象迭代器介面,完成對聚合物件的遍歷,同時在對聚合進行遍歷時跟蹤其當前位置
-
Aggregate(抽象聚合類)
抽象聚合類用於儲存物件,並定義建立相應迭代器物件的介面,宣告一個 createIterator() 方法用於建立一個迭代器物件
-
ConcreteAggregate(具體聚合類)
具體聚合類實現了建立相應迭代器的介面,實現了在聚合類中宣告的 createIterator() 方法,該方法返回一個與具體聚合對應的具體迭代器 ConcreteIterator 例項
模式分析
儲存資料是聚合物件的最基本職責,其中包含儲存資料的型別、儲存空間的大小、儲存空間的分配,以及儲存的方式和順序。然而,聚合物件除了能儲存資料外,還必須提供遍歷訪問其內部資料的方式,同時這些遍歷方式可能會根據不同的情形提供不同的實現。
因此,聚合物件主要有兩個職責:一是儲存內部資料;二是遍歷內部資料。前者是聚合物件的基本功能,後者是可以分離的。根據單一職責原則,物件承擔的職責越少,物件的穩定性就越好,我們將遍歷聚合物件中資料的行為提取出來,封裝到一個迭代器中,通過專門的迭代器來遍歷聚合物件的內部資料。迭代器模式是單一職責原則的完美體現。
下面通過一個簡單的自定義迭代器來分析迭代器模式的結構
首先定義一個簡單的迭代器去介面
public interface MyIterator {
void first(); // 訪問第一個元素
void next(); // 訪問下一個元素
boolean isLast(); // 判斷是否是最後一個元素
Object currentItem(); // 獲取當前元素
}
然後需要定義一個聚合介面
public interface MyCollection {
// 返回一個 MyIterator 迭代器物件
MyIterator createIterator();
}
定義好抽象層之後,我們需要定義抽象迭代器介面和抽象聚合介面的實現類,一般將具體迭代器類作為具體聚合類的內部類,從而迭代器可以實現直接訪問聚合類中的資料
public class NewCollection implements MyCollection {
private Object[] obj = {"dog", "pig", "cat", "monkey", "pig"};
public MyIterator createIterator() {
return new NewIterator();
}
private class NewIterator implements MyIterator {
private int currentIndex = 0;
public void first() {
currentIndex = 0;
}
public void next() {
if(currentIndex < obj.length) {
currentIndex++;
}
}
public boolean isLast() {
return currentIndex == obj.length;
}
public void currentItem() {
return obj[currentIndex];
}
}
}
NewCollection 類實現了 MyCollection 介面,實現了 createIterator() 方法,同時定義了一個陣列用於儲存資料元素,還定義了一個實現了 MyIterator 介面的內部類,索引變數 currentIndex 用於儲存所操作的陣列元素的下標值。客戶端程式碼如下:
public class Client {
public static void process(MyCollection collection) {
MyIterator i = collection.createIterator();
while(!i.isLast()) {
System.out.println(i.currentItem().toString());
i.next();
}
}
public static void main(String args[]) {
MyCollection collection = new NewCollection();
process(collection);
}
}
除了使用內部類實現之外,也可以使用常規的方式來實現迭代器
public class ConcreteIterator implements Iterator {
private ConcreteAggregate objects;
public ConcreteIterator(ConcreteAggregate objects) {
this.objects = objects;
}
public void first() {
...
}
public void next() {
...
}
public boolean isLast() {
...
}
public void currentItem() {
...
}
}
public class ConcreteAggregate implements Aggregate {
...
public Iterator createIterator() {
return new ConcreteIterator(this);
}
}
迭代器模式中應用了工廠方法模式,聚合類充當工廠類,而迭代器充當產品類
模式優缺點
迭代器模式優點:
- 支援以不同的方式遍歷一個聚合物件。在迭代器模式中只需要用一個不同的迭代器來替換原有迭代器即可改變遍歷演算法,也可以自己定義迭代器的子類以支援新的遍歷方式。
- 迭代器簡化了聚合類。原有聚合物件不再需要自行提供遍歷資料等操作方法。
- 在同一個聚合上可以有多個遍歷。由於每個迭代器都保持自己的遍歷狀態,因此可以對一個聚合物件進行多個遍歷操作。
- 增加新的聚合類和迭代器類都很方便,無須修改原始碼,滿足開閉原則。
迭代器模式缺點:
- 由於迭代器模式將儲存資料和遍歷資料的職責分離,增加新的聚合類需要對應增加新的迭代器類,類的個數成對增加,在一定程度上增加了系統的複雜性
模式適用環境
在以下情況可以使用迭代器模式:
- 訪問一個聚合物件的內容而無須暴露它的內部表示
- 需要為聚合物件提供多種遍歷方式
- 為遍歷不同的聚合結構提供一個統一的介面
Java 迭代器
Java 中的集合框架 Collections,其基本介面層次結構如圖
Collection 是所有集合類的根介面,它的主要方法如下:
boolean add(Object c);
boolean addAll(Collection c);
boolean remove(Object o);
boolean removeAll(Collection c);
boolean remainAll(Collection c);
Iterator iterator();
Collection 的 iterator() 方法返回一個 java.util.Iterator 型別的物件,而其子介面 java.util.List 的 listIterator() 方法返回一個 java.util.ListIterator 型別的物件,ListIterator 是 Iterator 的子類,它們構成了 Java 語言對迭代器模式的支援。
在 JDK 中,Iterator 介面具有如下三個基本方法:
- Object next():通過反覆呼叫 next() 方法可以逐個訪問聚合中的元素
- boolean hasNext():用於判斷聚合物件在是否還存在下一個元素,為了不丟擲異常,必須在呼叫 next() 之前先呼叫 hasNext()。如果迭代物件仍然擁有可供訪問的元素,那麼 hasNext() 返回 true
- void remove():刪除上一次呼叫 next() 時返回的元素
Java 迭代器可以理解為它工作時在聚合物件的各個元素之間,每呼叫一次 next() 方法,迭代器便越過下個元素,並且返回它剛越過的那個元素的地址引用。
在第一個 next() 方法被呼叫時,迭代器由“元素1”與“元素2”之間移動至“元素2”與“元素3”之間,跨越了“元素2”,因此 next() 方法將返回對“元素2”的引用;在第二個 next() 方法被呼叫時,迭代器由“元素2”與“元素3”之間移至“元素3”與“元素4”之間,next() 方法將返回對“元素3”的引用,此時呼叫 remove() 方法,則可將”元素3“刪除。
需要注意的是,next() 方法與 remove() 方法的呼叫是相互關聯的。如果呼叫 remove() 之前沒有先對 next() 進行呼叫,那麼將丟擲異常,因為沒有任何可供刪除的元素