假如你有一間早餐店,菜單用 ArrayList 存,有一間午餐店,菜單用 Array 存。今天你想把兩間餐廳合併成一間,但是馬上就遇到問題:要顯示菜單給顧客看時,要分別使用 for 來走訪 ArrayList 跟 Array,這樣造成程式維護上的困難,因為要寫兩次 for,未來要是合併另一間餐廳,又使用不同型態存菜單的話,程式碼不就又要大改? 是否有方法可以封裝重複的動作呢? 答案是可以的喔,讓我們直接看程式碼來了解怎麼做。
看到這裡,可能有人有疑問,Java 中的 Collection 都有支援 Iterator,為什麼還要自己寫一個呢?在這邊只是為了要解說 Iterator 是怎麼被實作的,當然上面的程式碼也能改成實作 Java 內的 Iterator。看完程式碼後,接下來看看反覆器模式的正式定義吧:
// 這個就是菜單的共通介面 // 為了能夠走訪所有菜色 public interface Iterator { // 得知是否還有更多元素 boolean hasNext(); // 取得下一個元素 Object next(); }
// 只是一個簡單的介面 // 可以取得一個反覆器, 取得菜單內容 public interface Menu { Iterator createIterator(); }
// 以午餐店, 使用 Array 當例子 public class LunchMenuIterator implements Iterator { private MunuItem[] mItems; private int mPosition = 0; // 紀錄目前走訪的位置 // 建構式傳入一個菜單 Array public LunchMenuIterator(MenuItem[] items) { mItems = items; } @Override public Object next() { // 傳回目前位置的元素, 並增加位置 MenuItem menuItem = mItems[position]; position = position + 1; return menuItem; } @Override public boolean hasNext() { // 因為是 Array, 要檢查是否超出長度 if(position >= mItems.length || mItems[position] == null) { return false; } else { return true; } } }
public class LunchMenu implements Menu { private static final int MAX_ITEMS = 6; MenuItem[] mMenuItems; // 省略建構式及其他方法... @Override public Iterator createIterator() { return new LunchMenuIterator(mMenuItems); } }修改好菜單後,就可以來看如何使用。假設店裡的服務生的工作之一就是要給客人看菜單:
public class Waitress { private Menu mBreakfast; private Menu mLunch; public Waitress(Menu breakfast, Menu lunch) { mBreakfast = breakfast; mLunch = lunch; } public void printMenu() { Iterator breakfastIterator = mBreakfast.createIterator(); Iterator lunchIterator = mLunch.createIterator(); printMenu(breakfastIterator); printMenu(lunchIterator); } public void printMenu(Iterator iterator) { while(iterator.hasNext()) { MenuItem menuItem = (MenuItem)iterator.next(); System.out.print(menuItem.getName() + ", "); System.out.println(menuItem.getPrice()); } } // 可能還有其他方法... }
反覆器模式讓我們能夠取得一個聚集內的每一個元素,而不需要此聚集將其實踐方式暴露出來
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
從類別圖可以看到,有一個共同的介面供所有的聚集所使用 (如 Java 中的 List),可以讓客戶程式碼從聚合的具象類別中鬆綁。而 Iterator 就是所有反覆器都要實作的介面。Iterator 可以使用 Java 現有的,也可以自己建立。具象聚集持有一個物件的聚合,並實作一個方法,可以取出相關反覆器。具象聚集為了能傳出反覆器,要負責實體化一個具象反覆器,此反覆器能夠走訪聚集內的每個元素。
另外值得一提的是,此模式很像之前介紹過的工廠方法模式,因為這兩個模式都是由次類別決定要建立的物件為何。
接下來介紹一個新的設計守則,單一責任守則:
一個類別應該只有一個改變的理由。
以這次介紹的反覆器模式來看,要是我們的聚集可以處理管理的行為,又能處理反覆的方法,這樣就有兩個理由要修改聚合程式。此守則告訴我們,盡量讓每個類別保持單一責任,就是減少改變的理由。因為類別有越多理由要修改,就越可能造成整個系統出錯。
最後再介紹一個名詞:內聚力(cohesion)。當一個類別或模組被設計成只支援一組相關功能時,可以說它有很高的內聚力,反之就是低的內聚力。遵守單一責任守則的類別容易有高的內聚力,且比有許多責任的低內聚力類別容易維護。
參考資料:
深入淺出設計模式(Head First Design Patterns)
深入淺出設計模式(Head First Design Patterns)
留言
張貼留言