java設計模式----迭代器模式
- 定義
在軟體構建過程中,集合物件內部結構常常變化各異,但對於這些集合物件,我們希望在不暴露其內部結構的同時,可以讓外部客戶程式碼透明地訪問其中包含的元素;同時這種“透明遍歷”也為同一種演算法在多種集合物件上進行操作提供了可能。
使用面向物件技術將這種遍歷機制抽象為“迭代器物件”為“應對變化中的集合物件”提供了一種優雅的方式。
迭代子(Iterator)模式又叫遊標(Cursor)模式,是物件的行為模式。迭代子模式可以順序地訪問一個聚集中的元素而不必暴漏聚集的內部表象。
- 使用場景
遍歷一個容器物件時
- 為什麼聚集需要迭代子
多個物件聚在一起形成的總體稱之為聚集(Aggregate),聚集物件是能夠包容一組物件的容器物件。聚集依賴於聚集結構的抽象化,具有複雜性和多樣性,陣列就是最基本的聚集,也是其他Java聚集物件的設計基礎。
Java聚集(Collection)物件是實現了共同的java.util.Collection介面的物件,是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()以得到一個迭代子物件。在得到迭代子的例項後,客戶端開始迭代過程,打印出所有的聚集元素。
迭代子模式優點
迭代子模式簡化了聚集的介面。迭代子具備了一個遍歷介面,這樣聚集的介面就不必具備遍歷介面。
每一個聚集物件都可以有一個或多個迭代子物件,每一個迭代子的迭代狀態可以是彼此獨立的。因此,一個聚集物件可以同時有幾個迭代在進行之中。
由於遍歷演算法被封裝在迭代子角色裡面,因此迭代的演算法可以獨立於聚集角色變化。