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