外觀模式是一種使用頻率非常高,但理解較爲簡單的設計模式,通過引入一個外觀角色來簡化客戶端與子系統之間的操作,爲複雜的子系統調用提供一個統一的入口,使子系統與客戶端的耦合度降低,且客戶端調用非常方便。
模式動機
在大多數情況下,一個網站都會提供一個網站首頁。網站首頁一般作爲整個網站的入口,提供了通往各個子欄目的超鏈接,用戶通過該首頁即可進入子欄目獲取所需信息。對於用戶而言只需記住網站首頁網站 URL,而無須記住每個子欄目的網址。網站首頁作爲用戶訪問子欄目的入口,是網站的外觀角色。用戶通過它可以方便地訪問子欄目,當然也可以繞過它直接訪問子欄目。
如果沒有外觀角色,即沒有爲網站提供一個首頁,每個用戶需要記住所有子欄目的 URL,不僅會讓系統耦合度增加,用戶與系統的交互也會異常複雜。
模式定義
外部與一個子系統的通信必須通過一個統一的外觀對象進行,爲子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加易於使用。
模式結構
外觀模式沒有一個一般化的類圖描述,這裏以類圖作爲外觀模式的描述形式之一。
-
Facade(外觀角色)
在客戶端可以調用這個角色的方法,在外觀角色中可以知道相關子系統的功能和責任;在正常情況下,它將所有從客戶端發來的請求委派到相應子系統,傳遞給相應的子系統對象處理。
-
SubSystem(子系統角色)
在軟件系統中可以同時有一個或多個子系統角色,每一個子系統都可以被客戶端直接調用,或被外觀角色調用,處理外觀類傳過來的請求;子系統並不知道外觀的存在,對於子系統而言,外觀僅僅是另外一個客戶端而言。
實例之電源總開關
一個電源總開關可以控制四盞燈、一個風扇、一臺空調和一臺電視機的啓動和關閉。通過該電源總開關可以同時控制所有上述電器設備,使用外觀模式設計該系統。
-
子系統類 Light
public class Light { private String position; public Light(String position) { this.position = position; } public void on() { System.out.println(this.position + "燈打開"); } public void off() { System.out.println(this.position + "燈打開"); } }
-
子系統類 Fan
public class Fan { public void on() { System.out.println("風扇打開"); } public void off() { System.out.println("風扇關閉"); } }
-
子系統類 AirConditioner
public class AirConditioner { public void on() { System.out.println("空調打開"); } public void off() { System.out.println("空調關閉"); } }
-
子系統類 Televison
public class Television { public void on() { System.out.println("電視機打開"); } public void off() { System.out.println("電視機關閉"); } }
-
外觀類 GeneralSwitchFacade
public class GeneralSwitchFacade { private Light[] lights = new Light[4]; private Fan fan; private AirConditioner ac; private Television tv; public GeneralSwitchFacade() { lights[0] = new Light("左前"); lights[1] = new Light("右前"); lights[2] = new Light("左後"); lights[3] = new Light("右後"); fan = new Fan(); ac = new AirConditioner(); tv = new Television(); } public void on() { lights[0].on(); lights[1].on(); lights[2].on(); lights[3].on(); fan.on(); ac.on(); tv.on(); } public void off() { lights[0].off(); lights[1].off(); lights[2].off(); lights[3].off(); fan.off(); ac.off(); tv.off(); } }
-
測試類 Client
public class Client { public static void main(String[] args) { GeneralSwitchFacade gsf = new GeneralSwitchFacade(); gsf.on(); System.out.println("-------------"); gsf.off(); } }
-
運行結果
模式優缺點
外觀模式優點如下:
- 對客戶屏蔽子系統組件,使得子系統使用起來更加方便
- 實現了子系統和客戶之間的鬆耦合關係,子系統組件變化不會影響到客戶類
- 簡化了系統在不同平臺之間的移植過程,因爲一個子系統的修改對其他子系統沒有任何影響
- 只是提供了一個訪問子系統的統一入口,並不影響用戶直接使用子系統類
外觀模式缺點如下:
- 不能很好限制客戶使用子系統類
- 在不引入抽象外觀類的情況下,增加新的子系統可能需修改外觀類和客戶端的源代碼,違背了開閉原則
模式適用環境
以下情況可以使用外觀模式:
- 當要爲一個複雜子系統提供一個簡單接口時可以使用外觀模式
- 客戶程序與多個子系統之間存在很大依賴性
- 在層次化結構中,可以使用外觀模式定義系統每一層的入口,層與層之間不直接聯繫,通過外觀類建立聯繫,降低層之間的耦合度
一個系統有多個外觀類
爲了節約資源,一般將外觀類設計爲單例類,但這並不意味整個系統只有一個外觀類。一個系統中可以設計多個外觀類,每個外觀類都負責和一些特定的子系統交互,向用戶提供相應的業務功能
不要試圖通過外觀類爲子系統增加新行爲
外觀模式的用意是爲子系統提供一個集中化和簡化的溝通渠道,而不是向子系統加入新的行爲,新的行爲的增加應通過修改原有子系統類或增加新的子系統類來實現,不能通過外觀類來實現
外觀模式和迪米特法則
迪米特法則要求你與直接的朋友通信。外觀模式創造出一個外觀對象,將客戶端所涉及的屬於一個子系統的協作夥伴的數量減到最少。外觀類充當了客戶類與子系統類之間的第三者,降低了客戶類與子系統之間的耦合度。外觀模式是實現代碼重構以達到迪米特法則要求的一個強有力的武器。
抽象外觀類的引入
外觀模式最大的缺點是違背了開閉原則,當增加新的子系統或移除子系統時需修改外觀類,可以通過引入抽象外觀類在一定程度上解決該問題,客戶端針對抽象外觀類編程。對於新的業務需求,不修改原有外觀類,對應增加一個新的具體外觀類,由新的具體外觀類來關聯新的子系統對象。