20個主題學會設計模式 — (一)跌跌撞撞學會Iterator迭代器模式

前言

嗨大家,經過了一些日子沒有寫文章,為自己找了很多藉口,覺得自己還不夠專業、覺得自己太忙要準備研究所,因此花了好多時間都在讀書理解,但卻缺少了最重要的一步 — 輸出。因此這次想新開幾個主題,一方面幫助自己回憶及複習,一方面也幫助一些也在學習設計模式的人,我會盡量用淺顯易懂的例子進行講解,敬請期待囉。

參考書籍與程式語言

這次的主題主要參考這本書圖解設計模式,裡面的內容較為淺顯易懂,使用的語言是Java,若大家想要看看書中範例也可以去購買此書。

接著就讓我們一起踏入設計模式的大門吧!

Photo by Nikola Knezevic on Unsplash

關於Iterator 迭代器模式

這個模式可能是最常見也最好理解的模式,因為如果你有開發經驗,你應該會聽過Iterator這個interface,它的功用就是讓我們可以像遍歷陣列一樣去遍歷物件。用下面例子來說明,我們都知道用for可以依序遍歷陣列的資料結構,但如果我今天也想循環遍歷set集合呢?這時候你就不得不學習Iterator了,從下面程式來看,我們將三個字串放入sampleSet中,接著再用iterator打印出來,這裡注意的是,setTest.iterator()這個方法直接把Iterator返回給我們,那麼他的底層到底是如何實現,就讓我們一步一步進行實現。

我們的目標

今天有個題目,我們有一副牌組,牌組中有很多牌,而我們想做的就是,我們希望可以自己實現下列的程式:

上述方法中,首先我們創造了幾張牌,並創造了一個牌堆,接著依序把牌放到牌堆裡面,再將牌堆內的牌打印出來,程式中我們可以看到我們想要用iterator不僅是遍歷java提供給我們的集合,也可以遍歷我們自己提供的物件Deck,那麼這樣的方法要怎麼實現呢?首先我們先將這class實現,再談談itorator設計模式。

1. Card 卡牌類別

話不多說,直接上程式,首先我們定義了一個SuitEnum來存放卡牌的花色,再創建一個Card物件,裡面包含卡牌數字及花色還有卡牌各個物件的get及set方法,這些該算是蠻基本的內容,也是本例子中最簡單的部分。

2. Aggregate 及 Deck

接著來到重頭戲,我們想創造一個具有iterator方法的Deck,所以我們才可以用Deck.iterator()方法來創造迭代器Iterator物件,來迭代我們的Deck。

這時你可能會想說,這還不簡單,我在Deck類別裡面直接加一個iterator方法不就解決了?沒錯,你說的完全是對的,但若你在加這個方法時寫成了iterater還是iterators,又或是你根本沒返回Iterator類,你返回的是其他資料型態,那其他使用者不就要再去看你程式怎麼寫,再來使用你的物件。這樣真的會心很累,我只是想循環你的Deck而已還這麼搞剛,這時解決問題的Aggregate出現了!

Aggregate是一個interface,它負責定義你的類創建Iterator應該按造順序訪問保存在內部元素的人。其中有個iterator方法。說了這麼多,其實它就是一個interface介面,如果你的類要去實現iterator方法,就應該要實現它,這麼一來就規定你的類中一定要按造Aggregate中的iterator方法進行實現了。

因此我們現在實現Deck類時,就應該要implements Aggregate這個介面,這邊如果你沒有實現iterator方法,那程式就會報錯,這算是基本interface的用法,這樣是不是就不會有方法寫錯、返回錯誤類的狀況了呢。

接著稍微談一下Deck中的組成,首先我們定義一個Card的陣列儲存所有放進來的卡牌,並定義一個 last索引,紀錄目前Deck中的卡牌數量,還有其他新增卡片,拿到卡片的方法。並實現了iterator方法,返回new DeckIterator(this),這邊的DeckIterator也就是我們後面要實現的Iterator類別。

3. DeckIterator及Iterator

我們可以先談談java提供給我們的Iterator這個interface的原始碼,我直接從java原始碼中複製過來,因為註解太長所以我把註解刪掉了。

上面可以發現如果要實現Iterator這個介面,你應該實現hasNext()、next()方法,hasNext返回的是一個布林值,用於告訴別人還有沒有沒迭代到的元素,next方法則是返回下一個迭代到的元素,這樣講有點抽象,我們就來實現一個DeckIterator試試看。

從上面程式我們先看一下初始化方法,初始化DeckIterator我們必須傳入一個Deck物件,在Deck中我們直接把this傳過去,並將當前的索引設置為0,接著實現hasNext()方法,當index小魚deck的長度時,代表我們還沒有迭代完Deck中的卡片元素,返回true還有元素沒有迭代完。

next則是返回index上的元素,這裡我們用deck中的方法getCardByIndex拿到目前索引的card,再返回card。

這裡比較有趣的是,next()方法基本上就定義了要怎麼把卡片拿出來,現在是將Deck中的card[]陣列依序拿出卡牌,那如果今天我想要倒序拿出卡牌怎麼做呢?我是要直接去改Deck中的getCardByIndex方法嗎?當然不是,我們要修改的僅僅是新增一個BackDeckIterator實現類,再更改類中的index,及返回的方式即可。

4. 回到我們的目標

此時再看下面程式,是不是清楚多了呢,我們實現了迭代器,這時你可能會心想,但這樣好麻煩呀,我們知道Deck中的cards是由陣列組成,那為什麼不要直接for來循環card陣列,多方便呀!

用iterator來實現
用for來實現

假設今天老闆說,我們以後Deck中的Card放棄用陣列來管理,我們改成用Vector取代呢,那麼假設你在這之前,有一百個class中有迭代過cards,那麼因為你之前用迭代陣列的方法已經行不通了,你只好回去慢慢改,一邊咒罵老闆。但今天你如果是使用iterator來迭代呢?你僅需要到deck中改掉關於cards的拿出方式和一些細節,再去更改DeckIterator中的next及hasNext方法其他程式通通不用修改,正常工作,真香。

結語

iterator設計模式應該算是設計模式中比較親民的一個概念,也蠻好理解的,其中比較需要注意的就是next方法中容易搞混應該返回當前元素還是下一個元素,答案是「返回當前元素,並指向下一個元素」,hasNext方法則是確定接下來能否使用next方法,看完這次的內容後,希望你也可以用不同的例子實現自己的迭代器,練習才能使自己的程式功力更進步,加油我們下次再見。

本篇github連結

我是Andy,謝謝你看完這篇文章,如果文章有幫助到你的話,希望不吝於幫我拍手 🙌🙌

--

--

Andy Cheng
Andy的趣味程式練功坊

若能將學到的知識轉化為易懂的文章,才能算是真正學會。這是我創建這個帳號的初衷。