跳到主要內容

中介者模式 (Mediator Pattern)

        中介者模式,又稱栛調者模式,直接照字面上解釋,就是有一個中介者負責處理事情。因為有一個中介者可以負責事情,如處理、傳遞、通知,就可以簡化物件之間的溝通和控制制方式,進而降低物件之間的耦合性(相依性,依賴性)。

中介者模式定義一個可以封裝一組物件互動的物件,可以使物件不用直接互相引用而降低耦合性,且可以獨立改變物件之間的互動關係。


Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.



        類別圖中的 Colleague 相關類別,指的就是實際上要做事,彼此可能因為不同的需求而會有相依性的類別。ConcreteColleague 之間不會直接溝通,而是透過 Mediator。看到這邊,可以順便跟表象模式來做個比較。這兩個模式都同樣有管理一群類別的味道,但不同之處是在於 Client 的角度。表象模式中,Client 是不會直接跟物件們溝通,都是透過表象來做事。而中介者模式中,Client 還是可以直接跟物件們溝通,只不過中介者模式是強調的是物件們不會直接溝通。另外表象模式跟中介者模式都是很好的認識極少化守則(又稱迪米特法則)實踐,表象模式是把這個守則應用在 Client 跟要溝通的物件們,而中介者模式是把這個守則應用在物件們之間的溝通。

        接下來看個簡單的範例吧。假設你現在要設計一個 GUI 的登入畫面,有四個元件:可以讓使用者輸入帳號密碼的 EditText,顯示帳號或是密碼有問題的 TextView,以及可以讓使用者確定要登入的 Button。一般來說,當帳號密碼有問題,登入 Button 就不能按,且 TextView 可能需要顯示提示訊息。當沒有使用中介者模式時,這些元件可能彼此都要互相知道才能達到上述需求,但加入了中介者模式後:
// Colleague 介面
public abstract class View {

    protected mediator mMediator;

    protected void setMediator(Mediator m)
    {
        mMediator = m;
 }

    public abstract void action();
}

public class TextView extends View {

    @Override
    public void action()
    {
        System.out.println("User touch TextView");
    }
}

public abstract class EditText extends View {

    @Override
    public void action()
    {
        System.out.println("User touch EditText");
 }
}

public class AccountEditText extends EditText {

    // 假設 EditText 有 text 改變的 callback
    @Override
    public void onTextChanged()
    {
        if(!isAccountValid())
        {
            mMediator.showAccountError();
        }
    }

    public boolean isAccountValid()
    {
        // 省略
    }
}

public class PasswordEditText extends EditText {

    // 假設 EditText 有 text 改變的 callback
    @Override
    public void onTextChanged()
    {
        if(!isPasswordValid())
        {
            mMediator.showPasswordError();
        }
    }

    public boolean isPasswordValid()
    {
        // 省略
    }
}

public class Button extends View {

    @Override
    public void action()
    {
        if(loginSuccess())
        {
            mMediator.loginSuccess();
        }
        else
        {
            mMediator.loginFail();
        }
    }
}

// Mediator 介面
public interface Mediator {

    void loginFail();
    void loginSuccess();
    void showAccountError();
    void showPasswordError();
}

// 實際的 Mediator 類別
// 負責處理使用者登入的不同情況的畫面顯示
public class LoginMediator implements Mediator {

    private TextView mHintView;
    private EditText mAccountView;
    private EditText mPasswordView;
    private Button mSubmitButton;

    public LoginMediator(TextView hintView, EditText accountView,
            EditText passwordView, Button submitButton)
    {
        mHintView = hintView;
        mAccountView = accountView;
        mPasswordView = passwordView;
        mSubmitButton = submitButton;
    }

    @Override
    public void loginFail()
    {
        // 登入失敗時, 提示使用者,
        // 並把帳號密碼清空
        mHintView.setText("Login failed");
        mSubmitButton.setEnabled(true);
        mAccountView.setText("");
        mPasswordView.setText("");
    }

    @Override
    public void loginFail()
    {
        // 可能跳到其他畫面,
        // 這個畫面可以不做事
        mHintView.setText("Login success");
    }

    @Override
    public void showAccountError()
    {
        // 帳號有問題, 提示使用者,
        // 並讓登入按鈕不能按
        mHintView.setText("User account error");
        mSubmitButton.setEnabled(false);
        mAccountView.setText("");
    }

    @Override
    public void showPasswordError()
    {
        // 密碼有問題, 提示使用者,
        // 並讓登入按鈕不能按
        mHintView.setText("User account error");
        mSubmitButton.setEnabled(false);
        mPasswordView.setText("");
    }
}

public class MediatorDemo {

    Mediator mMediator;

    public static void main(String[] args)
    {
        // 實例化所需 GUI 元件, 這邊參考就好
        TextView hintView = new TextView(...);
        EditText accountView = new AccountEditText(...);
        EditText passwordView = new PasswordEditText(...);
        Button submitView = new Button(...);

        mMediator = new LoginMediator(hintView, accountView,
                                      passwordView, submitView);

        hintView.setMediator(mMediator);
        accountView.setMediator(mMediator);
        passwordView.setMediator(mMediator);
        submitView.setMediator(mMediator);

        // 等待 GUI 事件...
    }

    // 假設我們有設定元件 touch 的 callback
    @Override
    public void onViewClicked(View view)
    {
        view.action();
    }
}
        可以看出上面的程式碼,各個元件不用知道彼此的情形,只要專心處理自己的事,元件之間所需要的互動就交給 Mediator 來負責就好。

        最後來總結一下中介者模式的優缺點。由上面的介紹可以知道,此模式最大的用意就是讓物件可以彼此鬆綁,進而增加物件的再利用。,而把控制邏輯集中管理 (都由 Mediator 處理),可以簡化系統維護。但要是中介者本身設計不好的話,中介者本身會很複雜。

參考資料:

        深入淺出設計模式(Head First Design Patterns)
        Mediator 模式

留言

這個網誌中的熱門文章

整理設計模式

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