某天你心血來潮,想在家裡準備家庭劇院組。你為此做了一番研究,找來了覺得適合的播放器、投影機、螢幕、音響等等的設備,當你要準備開始看電影時,你要做哪些事呢?
將這些動作寫成程式碼的話:
- 將燈光調暗
- 開啟螢幕
- 打開投影機
- 設定投影機輸入模式
- 打開音響
- 設定音響音量
- 打開播放器
- 開始播放
將這些動作寫成程式碼的話:
// 將燈光調暗到 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.
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)
深入淺出設計模式(Head First Design Patterns)

留言
張貼留言