跳到主要內容

反覆器模式 (Iterator Pattern)

        假如你有一間早餐店,菜單用 ArrayList 存,有一間午餐店,菜單用 Array 存。今天你想把兩間餐廳合併成一間,但是馬上就遇到問題:要顯示菜單給顧客看時,要分別使用 for 來走訪 ArrayList 跟 Array,這樣造成程式維護上的困難,因為要寫兩次 for,未來要是合併另一間餐廳,又使用不同型態存菜單的話,程式碼不就又要大改? 是否有方法可以封裝重複的動作呢? 答案是可以的喔,讓我們直接看程式碼來了解怎麼做。

// 這個就是菜單的共通介面
// 為了能夠走訪所有菜色
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());
        }
    }

    // 可能還有其他方法...
}

        看到這裡,可能有人有疑問,Java 中的 Collection 都有支援 Iterator,為什麼還要自己寫一個呢?在這邊只是為了要解說 Iterator 是怎麼被實作的,當然上面的程式碼也能改成實作 Java 內的 Iterator。看完程式碼後,接下來看看反覆器模式的正式定義吧:

反覆器模式讓我們能夠取得一個聚集內的每一個元素,而不需要此聚集將其實踐方式暴露出來
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)

留言

這個網誌中的熱門文章

整理設計模式

        依據 GOF 的書,可以將經典的設計模式分為以下三類:生成、行為、結構。 生成模式 :牽涉到 將物件實體化 。這類模式都提供一個方法,將客戶從所需要實體化的物件中鬆綁出來。 獨體模式 (Singleton Pattern) 工廠方法模式 (Factory Method Pattern) 抽象工廠模式 (Abstract Factory Pattern) 建立者模式 (Builder Pattern) 原型模式 (Prototype Pattern) 結構模式 :讓你 合成類別或物件到大型的結構 。 裝飾者模式 (Decorator Pattern) 轉接器模式 (Adapter Pattern) 表象模式 (Facade Pattern) 合成模式 (Composite Pattern) 代理人模式 (Proxy Pattern) 橋接模式 (Bridge Pattern) 享元模式 (Flyweight Pattern) 行為模式 :模述 類別和物件如何互動 ,以及 各自的責任 。 策略模式 (Strategy Pattern) 觀察者模式 (Observer Pattern) 命令模式 (Command Pattern) 樣板方法模式 (Template Method Pattern) 反覆器模式 (Iterator Pattern) 狀態模式 (State Pattern) 責任鏈模式 (Chain of Responsibility Pattern) 解譯器模式 (Interpreter Pattern) 中介者模式 (Mediator Pattern) 備忘錄模式 (Memento Pattern) 訪問者模式 (Visitor Pattern)         有人可能會覺得裝飾者模式明明有替物件增加行為,為什麼不算是行為模式呢?我們可以從上面的結構模式得知, 結構模式用來描述類別或物件如何被合成,以建立新的結構或功能 。裝飾者模式允許你透過「 將某物件包裝進另一個物件的方式 」,將物件合成以提供新功能,因此焦點應該放在「 動態合成物件,以取得某功能 」,而不是物件之間的溝通。         設入淺出設計模式也有提到一些使用設計模式的

訪問者模式 (Visitor Pattern)

        假設你設計一個系統,其中會有一些相似類別,類別中都有某些方法內容相似,但還是需要判斷目前要做事的是哪個類別才能呼叫對應的適當類別。通常遇到這種情情,在 Java 中最直接的做法就是使用 instanceof 關鍵字來判斷,如以下的簡單範例: public interface CarComponent { public void printMessage(); } public class Wheel implements CarComponent { @Override public void printMessage() { System.out.println("This is a wheel"); } // 這是 Wheel 跟 Engine 不同的方法 public void doWheel() { System.out.println("Checking wheel..."); } } public class Engine implements CarComponent { @Override public void printMessage() { System.out.println("This is a engine"); } // 這是 Wheel 跟 Engine 不同的方法 public void doEngine() { System.out.println("Testing this engine..."); } } public class Car { private List mComponents; public Car() { mComponents = new ArrayList<carcomponent>(); } // 有些時候我們還是需要針對不同類別去做不同的事情 public void setComponent(CarCompon

裝飾者模式 (Decorator Pattern)

        假如你有一間飲料店, 目前只有賣幾種咖啡。因為生意很好, 因此想更換菜單…         以下是目前菜單的類別圖:         簡單說明此類別圖, cost() 是抽象的, 子類別要實作自己的 cost() 來告知飲料的價格。         買咖啡時, 也可要求要加料, 例如牛奶(Milk)、摩卡(Mocha,就是巧克力口味)。這樣的新類別要如何設計呢 ? 看起來是不能直接新增所需的子類別, 例如 EspressoWithMilk, EspressoWithMilkAndMocha, DarkRoastWithMilk, DarkRoastWithMilkAndMocha… 這樣加下去, 日後飲料跟配料越來越多時, 類別也就越多, 這實在不是個好設計。         換個方式設計呢, 在 Beverage 裡面加入所有的配料如何 ? 這樣好像也不太好, 未來要是配料有更動, Beverage 程式碼就要重寫, 而未來要是有新口味的飲料時, 有些配料就不太合理 ( 薑茶加摩卡 ? ), 更麻煩的是, 無法應付機車的客人 (例如要加 3 份牛奶)。這時候裝飾者模式就能上場啦。在介紹裝飾者模式前, 先說明其設計守則: 類別應該開放, 以便擴充 ; 應該關閉, 禁止修改。         我們的目標是允許類別容易擴充, 在不修改現有程式碼的情形就能搭配新的行為。這樣的設計具有彈性, 可以接受新功能以達到改變需求的目的。這看起來好像有點矛盾, 但是的確有一些技術可以在不直接修改程式碼的情形下進行擴充, 如裝飾者模式。         這時候應該有人會問: 那是不是以後我的專案架構設計都遵循這個守則就是好設計了 ? 答案是不太可能, 也沒這必要, 就算做得到, 也可能是浪費, 容易導致程式碼複雜且難以理解。只需小心選擇哪些部分未來會擴充, 這些部份遵循這個設計守則即可。         接下來正式介紹裝飾者模式的定義:  裝飾者模式動態地將責任加諸於物件上。若要擴充功能,裝飾者模提供了比繼承更有彈性的選擇 Attach additional responsibilities to an object dynamically. Decorators provide a flexible alt