模式動機
引入外觀角色之后,用戶只需要直接與外觀角色交互,用戶與子系統之間的復雜關系由外觀角色來實現,從而降低了系統的耦合度。
模式定義
外觀模式(Facade Pattern):外部與一個子系統的通信必須通過一個統一的外觀對象進行,為子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。外觀模式又稱為門面模式,它是一種對象結構型模式。
模式結構
外觀模式包含如下角色:
• Facade:
外觀角色
• SubSystem:子系統角色
模式分析
根據“單一職責原則”,在軟件中將一個系統划分為若干個子系統有利於降低整個系統的復雜性,一個常見的設計目標是使子系統間的通信和相互依賴關系達到最小,而達到該目標的途徑之一就是引入一個外觀對象,它為子系統的訪問提供了一個簡單而單一的入口。
外觀模式也是“迪米特法則”的體現,通過引入一個新的外觀類可以降低原有系統的復雜度,同時降低客戶類與子系統類的耦合度。
外觀模式要求一個子系統的外部與其內部的通信通過一個統一的外觀對象進行,外觀類將客戶端與子系統的內部復雜性分隔開,使得客戶端只需要與外觀對象打交道,而不需要與子系統內部的很多對象打交道。
外觀模式的目的在於降低系統的復雜程度。
外觀模式從很大程度上提高了客戶端使用的便捷性,使得客戶端無須關心子系統的工作細節,通過外觀角色即可調用相關功能。
典型的外觀角色代碼:
1 public class Facade 2 { 3 private SubSystemA obj1 = new SubSystemA(); 4 private SubSystemB obj2 = new SubSystemB(); 5 private SubSystemC obj3 = new SubSystemC(); 6 public void method() 7 { 8 obj1.method(); 9 obj2.method(); 10 obj3.method(); 11 } 12 }
外觀模式實例與解析
實例一:電源總開關
• 現在考察一個電源總開關的例子,以便進一步說明外觀模式。為了使用方便,一個電源總開關可以控制四盞燈、一個風扇、一台空調和一台電視機的啟動和關閉。通過該電源總開關可以同時控制上述所有電器設備,使用外觀模式設計該系統。
實例代碼(JAVA):
1 //外觀角色類 2 public class GeneralSwitchFacade { 3 private Light lights[]=new Light[4]; 4 private Fan fan; 5 private AirConditioner ac; 6 private Television tv; 7 8 public GeneralSwitchFacade() 9 { 10 lights[0]=new Light("左前"); 11 lights[1]=new Light("右前"); 12 lights[2]=new Light("左后"); 13 lights[3]=new Light("右后"); 14 fan=new Fan(); 15 ac=new AirConditioner(); 16 tv=new Television(); 17 } 18 19 public void on() 20 { 21 lights[0].on(); 22 lights[1].on(); 23 lights[2].on(); 24 lights[3].on(); 25 fan.on(); 26 ac.on(); 27 tv.on(); 28 } 29 30 public void off() 31 { 32 lights[0].off(); 33 lights[1].off(); 34 lights[2].off(); 35 lights[3].off(); 36 fan.off(); 37 ac.off(); 38 tv.off(); 39 } 40 } 41 42 //子系統角色類 43 public class Light 44 { 45 private String position; 46 47 public Light(String position) 48 { 49 this.position=position; 50 } 51 52 public void on() 53 { 54 System.out.println(this.position + "燈打開!"); 55 } 56 57 public void off() 58 { 59 System.out.println(this.position + "燈關閉!"); 60 } 61 } 62 63 //子系統角色 64 public class Fan 65 { 66 public void on() 67 { 68 System.out.println("風扇打開!"); 69 } 70 71 public void off() 72 { 73 System.out.println("風扇關閉!"); 74 } 75 } 76 77 //子系統角色 78 public class AirConditioner 79 { 80 public void on() 81 { 82 System.out.println("空調打開!"); 83 } 84 85 public void off() 86 { 87 System.out.println("空調關閉!"); 88 } 89 } 90 91 //子系統角色 92 public class Television 93 { 94 public void on() 95 { 96 System.out.println("電視機打開!"); 97 } 98 99 public void off() 100 { 101 System.out.println("電視機關閉!"); 102 } 103 } 104 105 //客戶端 106 public class Client 107 { 108 public static void main(String args[]) 109 { 110 GeneralSwitchFacade gsf=new GeneralSwitchFacade(); 111 gsf.on(); 112 System.out.println("-----------------------"); 113 gsf.off(); 114 } 115 }
實例二:文件加密
• 某系統需要提供一個文件加密模塊,加密流程包括三個操作,分別是讀取源文件、加密、保存加密之后的文件。讀取文件和保存文件使用流來實現,這三個操作相對獨立,其業務代碼封裝在三個不同的類中。現在需要提供一個統一的加密外觀類,用戶可以直接使用該加密外觀類完成文件的讀取、加密和保存三個操作,而不需要與每一個類進行交互,使用外觀模式設計該加密模塊。
模式優缺點
優點
• 對客戶屏蔽子系統組件,減少了客戶處理的對象數目並使得子系統使用起來更加容易。通過引入外觀模式,客戶代碼將變得很簡單,與之關聯的對象也很少。
• 實現了子系統與客戶之間的松耦合關系,這使得子系統的組件變化不會影響到調用它的客戶類,只需要調整外觀類即可。
• 降低了大型軟件系統中的編譯依賴性,並簡化了系統在不同平台之間的移植過程,因為編譯一個子系統一般不需要編譯所有其他的子系統。一個子系統的修改對其他子系統沒有任何影響,而且子系統內部變化也不會影響到外觀對象。
• 只是提供了一個訪問子系統的統一入口,並不影響用戶直接使用子系統類。
缺點
• 不能很好地限制客戶使用子系統類,如果對客戶訪問子系統類做太多的限制則減少了可變性和靈活性。
• 在不引入抽象外觀類的情況下,增加新的子系統可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。
模式適用環境
在以下情況下可以使用外觀模式:
• 當要為一個復雜子系統提供一個簡單接口時可以使用外觀模式。該接口可以滿足大多數用戶的需求,而且用戶也可以越過外觀類直接訪問子系統。
• 客戶程序與多個子系統之間存在很大的依賴性。引入外觀類將子系統與客戶以及其他子系統解耦,可以提高子系統的獨立性和可移植性。
• 在層次化結構中,可以使用外觀模式定義系統中每一層的入口,層與層之間不直接產生聯系,而通過外觀類建立聯系,降低層之間的耦合度。
模式應用
(1) 外觀模式應用於JDBC數據庫操作
1 public class JDBCFacade { 2 private Connection conn=null; 3 private Statement statement=null; 4 public void open(String driver,String jdbcUrl,String userName,String userPwd) { 5 ...... 6 } 7 public int executeUpdate(String sql) { 8 ...... 9 } 10 public ResultSet executeQuery(String sql) { 11 ...... 12 } 13 public void close() { 14 ...... 15 } 16 }
(2) Session外觀模式是外觀模式在Java EE框架中的應用
模式擴展
一個系統有多個外觀類
• 在外觀模式中,通常只需要一個外觀類,並且此外觀類只有一個實例,換言之它是一個單例類。在很多情況下為了節約系統資源,一般將外觀類設計為單例類。當然這並不意味着在整個系統里只能有一個外觀類,在一個系統中可以設計多個外觀類,每個外觀類都負責和一些特定的子系統交互,向用戶提供相應的業務功能。
不要試圖通過外觀類為子系統增加新行為
• 不要通過繼承一個外觀類在子系統中加入新的行為,這種做法是錯誤的。外觀模式的用意是為子系統提供一個集中化和簡化的溝通渠道,而不是向子系統加入新的行為,新的行為的增加應該通過修改原有子系統類或增加新的子系統類來實現,不能通過外觀類來實現。
外觀模式與迪米特法則
• 外觀模式創造出一個外觀對象,將客戶端所涉及的屬於一個子系統的協作伙伴的數量減到最少,使得客戶端與子系統內部的對象的相互作用被外觀對象所取代。外觀類充當了客戶類與子系統類之間的“第三者”,降低了客戶類與子系統類之間的耦合度,外觀模式就是實現代碼重構以便達到“迪米特法則”要求的一個強有力的武器。
抽象外觀類的引入
• 外觀模式最大的缺點在於違背了“開閉原則”,當增加新的子系統或者移除子系統時需要修改外觀類,可以通過引入抽象外觀類在一定程度上解決該問題,客戶端針對抽象外觀類進行編程。對於新的業務需求,不修改原有外觀類,而對應增加一個新的具體外觀類,由新的具體外觀類來關聯新的子系統對象,同時通過修改配置文件來達到不修改源代碼並更換外觀類的目的。