設計模式(十):從電影院中認識"迭代器模式"(Iterator Pattern)
上篇部落格我們從醋溜土豆絲與清炒苦瓜中認識了“”,那麼在今天這篇部落格中我們要從電影院中來認識"迭代器模式"(Iterator Pattern)。“迭代器模式”顧名思義就是通過迭代的形式來取出容器中的值。如果你對Java語言熟悉的話,那麼你應該使用過Java中的迭代器,迭代器一般使用hasNext()方法來判斷是否有下一個值,如果有下一個值的話,那麼就使用next()方法來獲取下一個值。本篇部落格中就從“電影院”中來認識一下這種“迭代器模式”,並且將陣列與字典使用迭代器進行遍歷。具體說來使用迭代器的陣列與字典對外所展現的遍歷方式是一致的,也就是說使用者在遍歷字典或者陣列時,所使用的方法是一致的。當然我們今天的任務是使用Swift語言去實現屬於Swift的迭代器,使用該迭代器來遍歷字典和陣列
今天部落格中所使用的場景與電影院有關,我們假設在一家商場中有兩家電影院,而商場中間的大螢幕會滾動展示兩家電影院所放映的電影。兩家電影院的區別就是其儲存電影資源的方式不同,一個是使用陣列來儲存的電影,一個是使用字典來儲存的電影。如果不使用迭代器模式的話,那麼兩家電影院所遍歷電影的方式肯定是不同的。今天部落格中我們先給出無“迭代器模式”中遍歷兩家電影資源的方式,然後在給出“迭代器模式”下遍歷兩家電影院中電影資源的方式。當然,在下方使用迭代器遍歷時,我們也使用了“工廠方法模式”,具體請參見下詳細實現。
下方是迭代器模式的定義:
迭代器模式:提供一種方法順序訪問一個聚合物件中的每個元素,而又不暴漏其內部的表示。
一、無“迭代器”的電影院
1.無“迭代器”電影院的類圖
在本篇部落格中的第一部分我們先給出無“迭代器”的電影院遍歷其電影資源的方式。因為不同電影院儲存電影資源的方式不同,所以在沒有迭代器模式下兩者的遍歷方式是不同的。在程式碼設計中我們一貫遵循“依賴介面程式設計,而不依賴具體實現”的原則,下方雖然沒有使用“迭代器模式”,但是我們還是要定義介面的。下方的類圖中的CinemaType01介面就是我們所有影院要實現的介面。在該協議中有一個display()方法,用來展示該影院正在放映的電影,而我們的Market(商場)類就是依賴於這個影院介面,在Market的類中要呼叫具體影院的display()方法來展示放映的電影。要說明一點就是CinemaType01協議是我們商場中給商場中的電影院所制定的規則,因為我們的Market要呼叫display()方法將商場中的影院所熱播的電影投影到大螢幕上,只要是入駐該Market的影院就得遵循CinemaType01協議,並實現display()方法
通過下方的類圖,我們容易看到,Market雖然是呼叫具體影院的display()方法。但是Market不依賴於影院的具體實現,而是依賴於影院的介面,也就是下方的CinemaType01協議。Cinema01和Cinema01兩個類就是我們商場中的兩個具體的電影院了。Cinema01中的items是個陣列,Cinema02中的是個字典。沒有使用“迭代器”的商場影院的整體設計的類圖如下所示:
2. 程式碼實現
上面的類圖是我們的設計,也就是類似於設計圖紙。接下來我們開始搬磚,要對上面的設計圖紙進行實現,也就是我們的程式碼實現。因該示例比較簡單,所以在該部分我們實現完畢後會給出相應的測試用例。測用例就相當於蓋好的商品房要做樣板間一樣呢。下方片段就是我們給出的程式碼實現。
在下方程式碼片段中,最上方就是我們所有電影院都要實現的協議CinemaType01,在該協議中我們聲明瞭display()方法來展示當前該影院放映的電影。緊接著該協議下方的兩個實現就是我們的兩個電影院Cinema01和Cinema02。在這兩個電影院中一個使用的Array來儲存的影片資源,一個使用的是Dictionary來儲存的電影資源。Cinema01中的display()所做的事情就是對陣列的遍歷,而Cinema02所做的事情就是對字典的遍歷。而我們的商場類Market就負責呼叫相應電影院中的display()方法來展示商場中電影院所熱播的電影。
在Market類中我們需要注意一下,所有影院是存在cinemas陣列中的。而我們在宣告cinemas的陣列型別時,為該陣列的泛型指定的是CinemaType01協議(也就是是介面),這說明cinemas儲存的是遵循CinemaType01協議的所有電影院,而不僅僅是這兩個電影院。這樣如果來了第三家電影院就可以無縫的給我們商場的大螢幕進行對接了。下方就是我們的具體實現。
3、測試用例
我們搬完磚蓋完房子了,接下來到了測試的時間了,下方就是我們的測試用例。因為該示例比較簡單,所以我們的測試用例也是比較簡單的,就下邊簡簡單單的幾行程式碼。雖然簡單,但是測試用例還是比較簡單的。下方我們建立了一個商場物件,並且給該商場物件指定了兩家電影院,也就是我們商場中所入駐的電影院。然後我們的商場會呼叫display()方法來列印所播放的電影,具體如下所示:
二、使用“迭代器”來規範商場中的影院
上面的設計似乎沒有什麼問題,也便於後期的擴充,在來一家影院的話,只要實現商場中定義的CinemaType01規則即可,也就是實現display()方法即可。因為我們的Market要呼叫這個display()方法。但是每個電影院中儲存電影資源的方式不同,導致display()方法各有不同。商場為了規範電影院,提出每個電影院要使用同一個的display()方法。因為這個要求是通過迭代器來實現的,所以我們將電影院中的display()方法進行了一個重新命名,我們將其重新命名為iteratorItem()。
下面我要引入迭代器,然後就可以將每個電影院中的display()方法進行提取了。我們引入迭代器後將display()方法重新命名為iteratorItem()。我們統一影院中的iteratorItem()方法的解決方案是引入迭代器。因為迭代器對外使用的方式是一樣的,我們可以為不同的資料型別指定不同的迭代器,比如陣列迭代器,字典迭代器。無論是什麼迭代器,外部使用該迭代器的方式是一致的,這樣就可以做到無論使用什麼型別來儲存電影資源,只要使用迭代器遍歷,其遍歷方式是不變的。可能此處說的有些抽象,接下來將會給出具體的設計細節與實現細節,來直觀的感受一下迭代器的魅力。有了迭代器我們就可以使用一個display()方法(也就是該部分的iteratorItem()方法)來遍歷不同型別的資料了。
1.引入“迭代器”後的電影院
還是老套路,首先我們會給出類圖的設計,然後再給出具體實現呢。下方就是我們引入“迭代器”後的類圖。大眼一看也許會有些抽象,客官莫著急,聽我慢慢道來。在下方類圖中大體分為三個模板一個是Market類,這個類與之前沒有什麼區別。綠框中是我們引入的迭代器,黃框中是我們重構後的電影院,在電影院使用迭代器後,我們在此使用了工廠方法模式,具體請看下方詳述。
我們先來看今天的主題,也就是迭代器的設計思路。下方綠框中是我們引入的迭代器。當然我們是依賴介面程式設計的,迭代器怎麼能沒有介面呢。在迭代器設計之初我們先給出迭代器的協議,也就是綠框中的Iterator協議。該協議中有兩個方法,一個是hasNext(),用來判斷是否有下一項。另一個是next()方法,如果有下一項,那麼就使用next()方法來獲取下一項。在遵循Iterator協議的基礎上我們給出了陣列迭代器ArrayIterator和字典迭代器DictionaryIterator的實現。在程式碼實現中我們會給出詳細的實現方式。
接著我們看黃框中的部分,也就是我們使用工廠方法模式重構後的電影院。在電影院協議CinemaType中定義了電影院中要實現的方法,其中的iteratorItem()方法就是我們上一部分的display()方法。在引入迭代器後,所有電影院使用迭代器進行遍歷元素的方式都是一樣的(都是使用hasNext()和next()方法),所有我們將iteratorItem()方法的預設實現放在了CinemaType協議的預設延展中。而createIterator()方法就是我們的工廠方法,它負責建立相應的迭代器。createIterator()方法依賴於迭代器的介面而不依賴於迭代器的具體實現。如果是對陣列進行遍歷,那麼該方法建立的就是陣列迭代器,如果是對字典遍歷,那麼建立的就是字典迭代器。
2. 上方類圖的具體實現
接下來我們有到了搬磚的時刻了,在該部分將會對上述類圖進行具體的程式碼實現,當然我們使用的仍然是Swift語言。如果你對其他語言使用起來更為得心應手,你就使用你拿手的面嚮物件語言來實現呢。下方我們會先給出迭代器的實現,然後在給出電影院的實現。Market類的結構基本不變。
(1)、實現我們的迭代器
從上面類圖的綠框中我們不難看出,我們要先給出迭代器協議的實現,然後給出陣列迭代器和字典迭代器的具體實現。下方程式碼片段就是對應著上方類圖中綠框部分的實現。Iterator協議中的內容比較簡單,就是聲明瞭外部使用迭代器的兩個方法:hasNext()和next()。
ArrayIterator就是我們實現的陣列迭代器。在ArrayIterator中對陣列進行了遍歷,position記錄了下一個元素的下標,hasNext()中所做的事情就是通過position來判斷是否有下個元素。next()就是通過position來獲取陣列中的值。DictionaryIterator就是我們建立的字典迭代器,該迭代器的功能是對字典進行遍歷的。其中的position也是用來記錄下一個元素下標的,其中的allKeys陣列用來獲取當前字典的所有key的,allKeys陣列通過當前的position來獲取key,然後通過key獲取該字典相應的值。具體實現方式如下所示:
(2)、在影院中使用上述迭代器
接下來就是規範影院的時候到了,商場規定入駐本商城的電影商家只需要選擇相應的迭代器即可。遍歷的預設實現會在電影院協議的預設延展中給出。下方程式碼片段就是使用迭代器後的影院實現。
CinemaType協議就是商場規定的影院協議,其中定義了兩個方法,createIterator()方法負責來建立特定的迭代器,iteratorItem()方法則負責使用createIterator()建立的迭代器來變數相應的資料。此處的createIterator()方法就是我們“工廠方法模式”中的工廠方法,也就是說在我們設計影院結構時我們使用了“工廠方法模式”。關於工廠方法模式的更多的細節,請參考之前的工廠模式主題的部落格《》,關於工廠模式在此就不做過多的贅述了。
extension CinemaType就是電影院協議的預設延展。其中給出了iteratorItme()方法的具體實現,該方法適用於所有迭代器的遍歷。因為所有迭代器都遵循於Iterator協議,都有hasNext()方法與next()方法,所有所有電影院都可以使用該方法來迭代遍歷自己的元素。
Cinema01和Cinema02這兩個類則是我們電影院的具體實現。兩個類都遵循CinemaType協議,並給出了createIterator()方法。Cinema01使用的是陣列來儲存的電影資源,所有建立的是陣列迭代器。Cinema02使用的是字典儲存的電影資源,所以建立的是字典迭代器。無論建立什麼樣的型別的迭代器,iteratorItme()方法都是可以正常使用的,這也就是使用迭代器的好處。
(3)、商場類與測試用例
經過上面的兩步,我們已經將迭代器的核心實現完畢。下方的程式碼就是我們的Market類與測試用例。Market類幾乎沒有變化,我們之前在Market中呼叫的是電影院中的display()方法,只不過現在我們是呼叫電影院的iteratorItem()方法。而Market類的使用方式沒有任何的變化,也就是我們的測試用例沒有任何的變化。下方就是我們的Market類與測試用例以及輸出結果。
至此我們的迭代器模式的完整例項已經實現完畢,其好處就是在於如果商場進入了第三家電影院,只需要遵循相應的協議並指定相應的迭代器即可。至於如何遍歷,交給我們的預設實現來做。