跳到主要內容

表象模式 (Facade Pattern)

        某天你心血來潮,想在家裡準備家庭劇院組。你為此做了一番研究,找來了覺得適合的播放器、投影機、螢幕、音響等等的設備,當你要準備開始看電影時,你要做哪些事呢?
  1. 將燈光調暗
  2. 開啟螢幕
  3. 打開投影機
  4. 設定投影機輸入模式
  5. 打開音響
  6. 設定音響音量
  7. 打開播放器
  8. 開始播放


        將這些動作寫成程式碼的話:
// 將燈光調暗到 10% 的亮度
lights.dim(10);

screen.on();

projecter.on();
projecter.setInput(player);

// 開啟喇叭
amp.on();
amp.setVolumn(5);

player.on();
player.play();
        這樣看起來做的事好像沒很多,但是當看完電影後,要把所有設備關掉要怎麼做?全部反向做一次嗎?假如要聽音樂而已,也要這麼麻煩嗎?未來升級新設備時,還要重新學習操作流程嗎?

        這時候最直覺的想法一定是:把這些事包成一個 function 就好啦。這就是表象模式的精神所在,將一個或數個類別複雜的一切都隱藏起來,只露出美好的表面(就是簡化介面啦)。

        使用表象模式,可以將一個複雜的次系統,變得容易使用。表象類別提供更合理的介面,來簡化原先的複雜介面。假如不想用表象介面時,還是可以直接操作次系統。

        在這邊特別說明一下,表象模式跟轉接器模式雖然都是用來封裝類別,但是他們的目的卻是不同的。轉接器模式的目的是改變介面以符合客戶的期望。而表象模式是提供次系統一個簡化的介面
public class HomeTheaterFacade {

    // 這就是合成
    // 會用到的次系統元件全都在這裡
    Amplifier mAmp;
    Player mPlayer;
    Projector mProjector;
    TheaterLights mLights;
    Screen mScreen;

    // 透過建構式將次系統元件記錄下來
    public HomeTheaterFacade(Amplifier amp,
                            Player player,
                            Projector projector,
                            TheaterLights lights,
                            Screen screen)
    {
        mAmp = amp;
        mPlayer = player;
        mProjector = projector;
        mLights = lights;
        mScreen = screen;
    }

    public void watchMovie(String movie)
    {
        System.out.println("Ready to watch a movie.");

        // 這邊就是將前面說的動作依序處理
        // 注意每件工作都是委由次系統的元件處理
        mLights.dim(10);
        mScreen.on();
        mProjector.on();
        mProjecter.setInput(mPlayer);
        mAmp.on();
        mAmp.setVolumn(5);
        mPlayer.on();
        mPlayer.play();
    }

    public void endMovie()
    {
        System.out.println("Ready to shut down theater.");

        // 關閉次系統中的所有元件
        mLights.on();
        mScreen.off();
        mProjector.off();
        mAmp.off();
        mPlayer.off();
    }
}
        程式碼很容易理解,不需多加說明,就只是把次系統的工作封裝起來而已。以下正式介紹表象模式的定義:

表象模式提供了一個統一的介面,用來存取次系統中的一群介面。表象定義了一個較高層次的介面,讓次系統更容易使用。
Provide a unified interface to a set of interfaces in a subsystem. Façade defines a higher-level interface that makes the subsystem easier to use.


        從類別圖更容易感受到表象模式「簡化」的功用。客戶只需要面對單一個介面,而不用去跟次系統中複雜的類別打交道。了解這個模式後,可以再順便學習一個設計守則,認識極少化守則 (Least Knowledge):只和你的密友談話

        這個設計守則的意思是,在設計一個系統時,不管是任何物件,都要注意它所互動的類別有哪些,以及如何互動。此守則是希望在設計中,不要有太多類別綁在一起,以免修改系統一部份會影響其它部份。如果類別之間互相依賴,就會變成一個易碎的系統,需要花許多時間維護。

        那要怎麼做到極少化守則呢?有幾個方向能參考。以某個物件而言,此物件的方法定義內,只能引用物件的某些方法,而這些方法必須屬於:
  • 該物件本身
  • 當作方法的參數傳進來的物件
  • 此方法建立或實體化的物件
  • 物件的任何元件
        這樣看可能不容易懂,我們以程式碼來說明:
public float getTemp()
{
    // 從氣象站取得了溫度計物件,
    // 再從該物件取得溫度,
    // 這樣就沒遵守認識極化守則
    Thrmometer thermometer = station.getThermometer();
    return thermometer.getTemperature();
}

public float getTemp()
{
    // 氣象站加入取得溫度的方法,
    // 減少所依賴的類別數目
    // 這樣就有遵守認識極化守則
    return station.getTemperature();
}
public class Car {

    // 這是類別裡的一個元件,
    // 可以呼叫他的方法
    private Engine mEngine;

    public void start(Key key)
    {
        // 在方法裡建立物件,
        // 可以呼叫他的方法
        Doors doors = new Doors();

        // key 是被當作參數傳進來,
        // 可以呼叫 key 的方法
        boolean authorized = key.turns();

        if(authorized)
        {
            // 可以呼叫物件所屬元件的方法
            engine.start();

            // 可以呼叫同一物件的 local method
            updateDashboardDisplay();

            // 可以呼叫方法內建立的物件的方法
            doors.lock();
        }
    }

    public updateDashboardDisplay()
    {
        // update display...
    }
}

        認識極少化守則跟其他守則一樣,並沒有要強制遵守,而是要考量所有的因素後再決定是否要遵守。雖然認識極少化守則減少了物件之間的相依性,可以減少軟體維護成本,但也會導致更多的「包裝」類別被製造出來以處理物件之間的構通。這可能導致複雜度及開發時程增加,並降低執行期的效率。

參考資料:

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