跳到主要內容

責任鏈模式 (Chain of Responsibility Pattern)

        假設你的公司時常會收到不同的電子郵件如垃圾信,顧客的抱怨信,顧客的感謝信等等,而公司希望對這些不同類型的信都先用制式的罐頭信回應 (就是不同的信件有不同的處理方式)。當你要寫程式處理公司的電子郵件時,直接寫在一個 class 是最簡單的作法。但你勢必會用到 if/else 或 switch 等來判斷不同的信件,而這樣做有一些缺點,例如程式複雜性增加,不夠彈性,擴充困難等等。當你希望多個物件 (處理信的方式) 都有機會能處理相同請求時 (新寄來的電子郵件) 時,可以考慮責任鏈模式。先介紹此模式的正式定義及類別圖:

讓多個物件都有機會可以處理請求,以避免請求的發送者和接受者之間產生耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞請求,直到有一個物件處理這個請求為止。
Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.


        從類別圖來看這個模式很單純,每個子類別都有要實作的方法來處理它們能處理的特殊請求。當有不能處理的請求時,也能傳給下一個子類別繼續處理。當然這邊就是由設計者自行決定要有幾個子類別來處理哪些不同的請求,以及決定這些子類別傳遞請求的順序為何。

        接著來看一下套用責任鏈模式的簡單程式碼吧
// 所有處理郵件的子類別要繼承的介面
public abstract class MailHandler {

    // 每個處理郵件類別都要記錄下一個
    // 能處理的人是誰
    protected MailHandler mHandler;

    public MailHandler(MailHandler handler)
    {
        mHandler = handler;
    }

    public void toNext(Mail mail)
    {
        if(mHandler != null)
        {
            mHandler.handleMail(mail);
        }
        else
        {
            // 沒有後繼者, 表示是尾端了
            // 通常可以用最一般化的處理
        }
    }

    public abstract void handleMail(Mail mail);
}

public class SpamMailHandler extneds MailHandler {

    @Override
    public void handleMail(Mail mail)
    {
        // 假設已經能知道郵件分類了
        if(mail.isSpam())
        {
            // 處理垃圾信
        }
        else
        {
            toNext(mail)
        }
    }
}

public class ThankMailHandler extneds MailHandler {

    ...
}

public class ComplainMailHandler extneds MailHandler {

    ...
}

public class GeneralMailHandler extends MailHandler {

    ...
}

// 可以這樣使用
MailHandler handler = new ThankMailHandler(
                      new ComplainMailHandler(
                      new SpamMailHandler(
                      new GeneralMailHandler(null))));
// 假設有一封郵件要處理
Mail mail = new Mail(...);
handler.hanldeMail(mail);

        從程式碼可以看到,我們在建構 MailHandler 物件時,同時也指定要接在之後處理的物件為何,當然這個順序是可以自定義的。通常都是由小範圍到大範圍,特殊情形至一般情形來組織責任鏈,如上例把 GeneralMailHandler 放在最後面。

        另外可能有人會覺得責任鏈物件生成的方式很像裝飾者模式,但這兩種模式意義卻是完全不同的喔。裝飾者模式的主要用途在增加元件的行為,而責任鏈模式是在組織處理請求的元件,範例中生成元件的部份也同時在指定元件順序,並沒有增加元件的行為。

        責任鏈模式可以將請求的發出者和接受者之間予以鬆綁。請求者不需要知道實際接受者是誰,也不用知道請求是如何被處理,各個接受者之間也是彼此獨立且鬆綁的。責任鏈模式也可以動態修改責任鏈,如新增或刪除處理請求的物件。因為上述的特性,因此很常用來使用在視窗程式中,處理像是滑鼠或是鍵盤事件。但要注意的是,此模式沒有保證請求一定會被處理,端看設計者如何設計責任鏈。

參考資料:

        深入淺出設計模式(Head First Design Patterns)
        Chain of Responsibility 模式
        [.NET]重構之路系列v11 –用責任鏈模式打破討厭的switch case

留言

這個網誌中的熱門文章

整理設計模式

        依據 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