迭代器模式 Iterator 行爲型 設計模式(二十)

迭代器模式(Iterator)
image_5c11cddc_d4
 
走遍天下,世界那麼大,我想去看看
 
image_5c11cddc_5f0c
在計算機中,Iterator意爲迭代器,迭代有重複的含義,在程序中,更有「遍歷」的含義
若是給定一個數組,咱們能夠經過for循環來遍歷這個數組,這種遍歷就叫作迭代
對於數組這種數據結構,咱們稱爲是可迭代的
因此
迭代器就是能夠用來對於一個數據集合進行遍歷的對象

意圖

提供一種方法,順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。
別名:遊標 Cursor

集合與遍歷

由一個或多個肯定的元素所構成的總體叫作集合
多個對象彙集在一塊兒造成的整體稱之爲彙集aggregate。
集合和彙集有類似的含義。
容器是指盛物品的器具,Java的Collection框架,就是設計用來保存對象的容器
容器能夠認爲是集合、彙集的具體體現形式,三個元素在一塊兒叫作集合(彙集),怎麼在一塊兒?數組,列表?這具體的體現形式就是容器
 
容器必須提供內部對象的訪問方式,若是不能獲取對象,容器也失去了存在的意義,一個只能進不能出的儲蓄罐你要它何用?
由於容器的存在就是爲了更方便的使用、管理對象。
並且一般須要容器提供對於內部全部元素的遍歷方法
 
然而容器其內部有不一樣的擺放形式,可順序,可無序堆集
簡言之,就是不一樣類型的容器必然有不一樣的內部數據結構
那麼,一種解決辦法就是不一樣的容器各自提供本身的遍歷方法
這樣的話,對於使用容器管理對象的客戶端程序來講:
若是迭代的邏輯,也就是業務邏輯沒有變化
當須要更換爲另外的集合時,就須要同時更換這個迭代方法
考慮這樣一個場景
有一個方法,
方法的參數類型爲 Collection
他的迭代邏輯,也就是業務邏輯爲遍歷全部元素,讀取每一個元素的信息,而且進行打印...
若是不一樣的容器有不一樣的遍歷方法,也就是一種實現類有一種不一樣的遍歷方法
一旦更換實現類,那麼就須要同步更換掉這個迭代方法,不然方法將沒法經過編譯
 
若是集合的實現不變,須要改變業務邏輯,也就是迭代的邏輯
那麼就須要修改容器類的迭代方法,也就是修改原來的遍歷方法
 
仍是上面的場景
有一個方法,方法的參數類型爲 Collection
他的迭代邏輯,也就是業務邏輯爲遍歷全部元素,讀取每一個元素的信息,而且進行打印...
如今他的實現類無需變化
可是業務邏輯須要變更,好比但願從後往前的方式進行遍歷,而再也不是從前日後
就須要修改原來的方法或者從新寫一個方法
 
 
出現上述問題的根本緣由就在於元素的迭代邏輯與容器自己耦合在一塊兒
當迭代邏輯或者集合實現發生變動時,須要進行修改,不符合開閉原則
容器自身不只僅須要存儲管理對象,還要負責對象的遍歷訪問,不符合單一職責原則
 
存儲管理對象是容器的核心職責,雖然常常須要提供遍歷方法,可是他並非核心職責
可是爲了提供遍歷元素的方法,可能不得不在容器類內提供各類全局變量,好比保存當前的位置等,這無疑也會致使容器彙集類設計的複雜度

結構

image_5c11cddc_779f
抽象迭代器角色Iterator
定義遍歷元素所須要的接口
具體的迭代器ConcreteIterator
實現了Iterator接口,而且跟蹤當前位置
抽象集合容器角色Aggregate
定義建立相應迭代器的接口(方法)
就是一個容器類,而且定義了一個返回迭代器的方法
具體的容器角色ConcreteAggregate
Aggregate的子類,而且實現了建立Iterator對象的接口,也就是返回一個ConcreteIterator實例
客戶端角色Client
持有容器對象以及迭代器對象的引用,調用迭代對象的迭代方法遍歷元素
 
迭代器模式中,經過一個外部的迭代器來對容器集合對象進行遍歷。
迭代器定義了遍歷訪問元素的協議方式。
容器集合對象提供建立迭代器的方法。

示例代碼

Aggregate角色
提供了iterator()獲取Iterator
package iterator;
public abstract class Aggregate {
abstract Iterator iterator();
abstract Object get(int index);
abstract int size();
}
ConcreateAggregate角色
內部使用一個Object數組,數組直接經過構造方法傳遞進去(只是爲了演示學習模式,不要糾結這算不上一個容器)
提供了大小的獲取方法以及獲取指定下標元素的方法
尤爲是實現了iterator()方法,建立一個ConcreteIterator實例,將當前ConcreteAggregate做爲參數傳遞給他的構造方法
package iterator;
public class ConcreateAggregate extends Aggregate {
 
private Object[] objects;
 
ConcreateAggregate(Object[] objects) {
this.objects = objects;
}
 
@Override
Iterator iterator() {
return new ConcreateIterator(this);
}
 
@Override
Object get(int index) {
return objects[index];
}
 
@Override
int size() {
return objects.length;
}
}
 
迭代器接口
一個是否還有元素的方法,一個獲取下一個元素的方法
package iterator;
public interface Iterator {
boolean hasNext();
Object next();
}
具體的迭代器
內部維護了數據的大小和當前位置
若是下標未到最後,那麼就是還有元素
next()方法用於獲取當前元素,獲取後當前位置日後移動一下
package iterator;
public class ConcreateIterator implements Iterator {
private Aggregate aggregate;
private int index = 0;
private int size = 0;
 
ConcreateIterator(Aggregate aggregate) {
this.aggregate = aggregate;
size = aggregate.size();
}
 
@Override
public boolean hasNext() {
return index < size ? true : false;
}
 
@Override
public Object next() {
Object value = aggregate.get(index);
index++;
return value;
}
}
測試類
package iterator;
public class Client {
public static void main(String[] args) {
Object[] objects = {"1", 2, 3, 4, 5};
Aggregate aggregate = new ConcreateAggregate(objects);
Iterator iterator = aggregate.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
image_5c11cddc_5524
示例代碼中ConcreateAggregate自己提供了獲取指定下標元素的方法,能夠直接調用獲取元素
藉助於Iterator,將迭代邏輯從Aggregate中剝離出來,獨立封裝實現
在客戶端與容器之間,增長了一層Iterator,實現了客戶端程序與容器的解耦
image_5c11cddc_440b
說白了,增長了Iterator,至關於經過Iterator封裝了真實容器對象的獲取元素的方法
不直接調用方法,通過Iterator轉換一層
 
並且仔細品味下,這有點「適配」的韻味,適配的目標就是統一的元素訪問協議,經過Iterator約定
而被適配的角色,則是真實容器對象元素的操做方法
總之「間接」「委託」「代理」的感受,對吧,好處本身品味

外部迭代與內部迭代

在上面的示例程序中,經過引入Iterator,實現了迭代邏輯的封裝抽象 
可是容器彙集對象自己有獲取元素的方法,因此客戶端仍舊能夠自行遍歷
Iterator也只不過是容器彙集對象的一個客戶而已
這種迭代器也叫作外部迭代器
對於外部迭代器有一個問題,對於不一樣的ConcreteAggregate,可能都須要一個不一樣的ConcreteIterator
也就是極可能會不得不建立了一個與Aggregate等級結構平行的Iterator結構,出現了不少的ConcreteIterator類
這勢必會增長維護成本
並且,雖然迭代器將客戶端的訪問與容器進行解耦,可是迭代器倒是必須依賴容器對象
也就是迭代器類ConcreteIterator與ConcreteAggregate必須進行通訊,會增長設計的複雜度,並且這也會增長類之間的耦合性
 
另外的一種方法是使用內部類的形式,也就是將ConcreteIterator的實現,移入到ConcreteAggregate的內部
藉助於內部類的優點:對外部類有充足的訪問權限,也就是無需擔憂爲了通訊要增長複雜度的問題
準確的說,你沒有任何的通訊成本,內部類能夠直接讀取外部類的屬性數據信息
並且,使用內部類的方式不會致使類的爆炸(儘管仍舊是會有另外一個class文件,可是從代碼維護的角度看算是一個類)
這種形式能夠叫作內部迭代器
 
不過不管哪一種方式,你能夠看得出來,使用迭代器的客戶端代碼,都是同樣的
藉助於工廠方法iterator()得到一個迭代器實例(簡單工廠模式)
而後藉助於迭代器進行元素遍歷

JDK中的迭代

咱們看下JDK中的Collection提供給咱們的迭代方式
Collection是全部集合的父類,Collection實現了Iterable接口
Iterable接口提供了iterator()方法用於返回一個Iterator類的一個實例對象
Iterator類提供了對元素的遍歷方法
image_5c11cddc_295f
接下來看下ArrayList的實現
ArrayList中iterator()返回了一個Itr對象,而這個對象是ArrayList的內部類,實現了Iterator接口
image_5c11cddc_26d
看得出來,java給集合框架內置了迭代器模式
在ArrayList中使用就是內部類的形式,也就是內部迭代器
boolean hasNext()
是否擁有更多元素,換句話說,若是next()方法不會拋出異常,就會返回true
next();
返回下一個元素
remove()
刪除元素
 
有幾點須要注意
1.)初始時,能夠認爲「當前位置」爲第一個元素前面
因此next()獲取第一個元素
image_5c11cddc_3d19
2.)根據第一點,初始的當前位置」爲第一個元素前面,因此若是想要刪除第一個元素的話,必須先next,而後remove
Iterator iterator = list.iterator();

iterator.next();

iterator.remove();

 

不然,會拋出異常
image_5c11cddc_6e70
3.)不只僅是刪除第一個元素須要先next,而後才能remove,每個remove,前面必須有一個next,成對出現
因此remove是刪除當前元素
若是下面這樣,會拋出異常
iterator.next();

iterator.remove();

iterator.remove();

 

image_5c11cddc_424a
4.)迭代器只能遍歷一次,若是須要從新遍歷,能夠從新獲取迭代器對象
若是已經遍歷到尾部以後仍舊繼續使用,將會拋出異常
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
}
iterator.next();
image_5c11cddc_7a48

總結

在java中萬事萬物都是對象
前面的命令模式將請求轉換爲命令對象
解釋器模式中,將語法規則轉換爲終結符表達式和非終結符表達式
迭代器模式中,將「遍歷元素」轉換爲對象
 
經過迭代器模式引入迭代器,將遍歷邏輯功能從容器彙集對象中分離出來
聚合對象自己只負責數據存儲,遍歷的職責交給了迭代器
 
對於同一個容器對象,能夠定義多種迭代器,也就是能夠定義多種遍歷方式
若是須要使用另外的迭代方式,僅僅須要更改迭代器對象便可
這樣你甚至能夠把ConcreteIterator使用配置文件進行注入,靈活設置 
 
將迭代遍歷的邏輯從容器對象中分離,必然會減小容器類的複雜程度
 
當增長新的容器類或者迭代器類時,不須要修改原有的代碼,符合開閉原則
 
若是你想要將容器彙集對象的遍歷邏輯從容器對象中的分離
或者想要提供多種不一樣形式的遍歷方式時,或者你想爲不一樣的容器對象提供一致性的遍歷接口邏輯
你就應該考慮迭代器模式了
迭代器模式的應用是如此普遍,以致於java已經將他內置到集合框架中了
因此對於咱們本身來講,多數時候能夠認爲迭代器模式幾乎用不到了
由於絕大多數時候,使用框架提供的應該就足夠了
在java實現中,迭代器模式的比較好的作法就是Java集合框架使用的這種形式---內部類形式的內部迭代器,若是真的須要本身搞一個迭代器,建議仿照集合框架搞吧
 
藉助於迭代器模式,若是迭代的邏輯不變,更換另外的集合實現,由於實現了共同的迭代器接口,因此不須要對迭代這塊,無需作任何變更
若是須要改變迭代邏輯,必須增長新的迭代形式,只須要增長一個新的內部類實現迭代器接口便可,其餘使用的地方只須要作很小的調整
ArrayList中的ListIterator<E> listIterator() 方法就是如此
有人以爲增長一個類和一個方法這不也是修改麼?我的認爲:開閉原則儘管最高境界是徹底的對擴展開放對修改關閉,可是也不能死摳字眼
增長了一個新的獲取迭代對象的方法以及一個新的類,總比將原有的源代碼中添加新的方法那種修改要強得多,全部的遍歷邏輯都封裝在新的迭代器實現類中,某種程度上能夠認爲並無「修改源代碼」
使用內部類的形式,有人以爲不仍是在一個文件中麼?可是內部類會有單獨的class文件,並且,內部類就像一道牆,分割了內外,全部的邏輯被封裝在迭代器實現類中
不須要影響容器自身的設計實現,因此也是符合單一職責原則的。
相關文章
相關標籤/搜索