HeadFirst 設計模式學習筆記9--迭代器模式

1.本節的一個話題引子是一個餐廳,它提供早餐和午餐,但是在訂製菜單的時候,早餐(Pancake)和午餐(Dinner)的實現卻造成了一些麻煩。訂製早餐的菜單是用ArrayList這樣一個數據池來維護的。但是訂製午餐的菜單則是一個標準數組進行維護。那麼在設計訂餐程序來遍歷這兩個不同的數據結構形成的菜單的時候,就會比較麻煩,畢竟返回的數據類型不是一樣的。(不知道我說清楚了沒,參閱英文版原書P321)

2.對於這樣的迭代場景,我們就可以使用所謂迭代器模式。它依賴於一個名爲迭代器的接口,然後讓所有相關的部分(在這個例子就是兩個菜單的迭代器)實現該迭代器。提供了一種方法順序訪問一個聚合對象的各個元素,同時又不暴露其內部的表示。具體的,我們先加入一個迭代器:

然後我們根據早餐和午餐具體的數據結構來實現這個接口:

 

通過上述實現接口的模式,我們把遊走的任務放在迭代器上,而不是具體聚合上,這樣既簡化了聚合的接口和實現,也讓責任各得其所。對於早餐和午餐的菜單,我們做如下的設計:

早餐:

  

午餐:

注意加粗的部分,我們加入了一個迭代器的工廠方法。此時,我們現在的服務生就可以從容的處理早餐和晚餐了:

我們下兩個單試一試:

3.這樣看上去代碼是增加了不少,但是對於兩個本來寫好的類(PancakeHouseMenu和DinerMenu),我們只在其中加入了createIterator這個方法,系統的耦合性很低。此時女招待這個類就不需要知道菜單具體的實現了,並且實現迭代器後我們只需一個循環就可以遍歷了。

 

4.其實,Java中就有迭代器,我們將PancakeHouseMenu和DinerMenu所擴展的接口換成java.util的迭代器接口即可,連ArrayList也有一個返回一個迭代器的iterator方法。這樣可以進一步減少依賴。首先,我們不需要去創建Iterator接口,直接使用JAVA中的迭代器接口來構造DinerMenuIterator:

注意:要是不想提供remove方法,那要怎麼辦呢?我們可以拋出一個UnsupportedOperationException異常表示不支持這樣的方法

而PancakeHouseMenu 則不需要再實現,因爲ArrayList直接有返回迭代器的相應方法。

我們現在開始實現菜單,等等!!

我們發現在兩個菜單中有一個共同的方法createIterator,那麼在這我們又有一個新的改進——可以通過面向接口編程,使二者統一起來:

早餐:

晚餐:

我們針對這個實現,來創建的女招待代碼就變得更加鬆耦合:

這樣我們通過接口抽象完成了迭代器和菜單的鬆耦合。但需要注意的是,這個線程不安全。當然,有人會問是不是可以實現向後移動,比如pervious(),那麼這時你還必須實現一個方法hasPervious()來驗證是不是已經到頂端。Java中有一個ListIterator可以供參考。

 

5.最後我們思考一個問題:那我們爲什麼不在這兩個聚合(早餐菜單和晚餐菜單)的內部實現迭代器功能的相關方法呢?我們可以分析一下這個問題如果答案是“可以”的話,將會帶來什麼。若這個集合改變的話,我們要修改這個類;若遍歷的方法改變的話,我們要修改這個類——我們至少有兩個引起類變化的原因。這會導致代碼的重構效率大大降低。

由此我們引出一個設計原則:單一原則——一個類應該只有一個引起變化的原因,類的每個職責都有改變的潛在區域,一旦超過職責就意味着超過“一個改變”的區域。這個原則極難遵守,因爲我們的大腦總習慣自然地把很多東西聚在一起。我們經常說“高內聚”,其實真正的含義在於——當一個模塊或一個類被設計爲只支持一組相關的功能時,我們稱其爲高內聚;反之,當被設計爲支持一組不相關的功能時,我們說其低內聚。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章