跳到主要內容

觀察者模式 (Observer Pattern)

        用來說明觀察者模式最常見也最容易懂的例子就是報社訂閱報紙:

  1. 報社的主要工作就是出版報紙 (提供資料)
  2. 用戶可向報社訂閱報紙。只要報社有出版新報紙, 而你在報社的訂閱名單中, 你都可以收到最新的報紙。
  3. 當你不想再看報紙時, 可隨時取消訂閱, 報社就不會送報紙到你家。
  4. 只要報社還存在, 用戶就可隨時向報社訂閱或取消訂閱報紙。
        假如你看得懂上面的例子的話, 其實也大概了解觀察者模式在幹什麼。把上面的例子, 報社改名為主題 (Subject), 訂閱者改為觀察者 (Observer), 觀察者模式 = 出版者 + 訂閱者

        更精確的觀察者模式定義為: 觀察者模式定義了物件之間的一對多關係, 如此一來, 當一個物件改變狀態, 其他相依者都會收到通知並自動被更新
Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

        以下來看個簡單的觀察者模式類別圖:

        主題介面定義了 registerObserver(), removeObserver(), 可以讓物件變成觀察者, 或從觀察者名單中移除, 而觀察者介面定義了 update(), 當主題有改變時, 可以用來通知所有觀察者。在此介紹觀察者模式的設計守則: 設計時, 盡量讓需要互動的物件之間關係鬆綁。此設計讓我們建立有彈性的 OO 系統, 能夠因應變化, 因為物件的相依性被降到最低。

  1. 關於觀察者的一切, 主題只知道觀察者有實作特定介面 (也就是 Observer 介面)。主題不需要知道觀察者的具體類別為何、做了什麼、及其細節。
  2. 任何時候都可以加入新的觀察者
  3. 有新型態的觀察者出現時, 主題的程式碼不用修改。主題不在乎觀察者實際類別, 只在乎有沒有實作觀察者介面。
  4. 片面改變主題或觀察者, 並不會影響另一方。只要兩邊的介面沒有更改就不會影響。

        了解上面的觀察者模式觀念後, 接下來就來完成報社的程式碼吧~

pubilc interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}

        主題介面定義了三個 method, 用來新增、刪除、以及通知觀察者。

public interface Observer {
    public void update(String content);
}

        觀察者介面只定義了一個 method, 當有資料更新時(如新報紙), 主題能通知觀察者。注意到我們這邊 update() 傳入的參數為 String 只是單純的範例, 實際上可根據不同需求來設計。

public class NewspaperOffice implements Subject() {

    // 使用 List 來記錄所有訂閱者
    List mObservers;

    // 報紙內容
    String mContent;

    public NewspaperOffice()
    {
        mObservers = new ArrayList();
        mContent = "";
    }

    public void sendNewspaper(String content)
    {
        mContent = content;
        notifyObservers();
    }

    @Override
    public void registerObserver(Observer o)
    {
        mObservers.add(o);
    }

    @Override
    public void removeObserver(Observer o)
    {
        int i = mObservers.indexOf(0);
        if(i > 0)
            mObservers.remove(i);
    }

    @Override
    public void notifyObservers()
    {
        for(int i = 0; i < mObservers.size(); i++)
        {
            Observer o = mObservers.get(i);
            o.update(mContent);
        }
    }
}

public class Customer implements Observer {

    // 訂閱者名稱
    String mName;

    public Customer(String name)
    {
        mName = name;
    }

    @Override
    public void update(String content)
    {
        System.out.println(name + "get newspaper: "
            + content);
    }
}

public class Test {
    public static void main(String[] args)
    {
        // 建立報社
        NewspaperOffice office = new NewspaperOffice();

        // 訂閱者 Tom
        Customer Tom = new Customer("Tom");

        // 訂閱者 Peter
        Customer Peter = new Customer("Peter");

        // 訂閱者訂報紙
        office.registerObserver(Tom);
        office.registerObserver(Peter);

        // 報社送報紙囉
        office.sendNewspaper("Newspaper 1...");

        // Tom 不想訂報紙了
        office.removeObserver(Tom);
        office.sendNewspaper("Newspaper 2...");
    }
}

        以上就是觀察者模式的基本觀念喔。可能有人會想說 update() 裡傳入的是固定參數, 要是未來需求改變, update() 不就要一直更改 ? 簡單的作法是主題提供一些 getter method,讓觀察者在收到 update() 時自己去向主題要自己感興趣的資料。其他更好的方法就自行思考囉。



參考資料:

深入淺出設計模式(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