### 一、模式定義
門面模式(Facade Pattern):外部與一個子系統的通信必須通過一個統一的外觀對象進行,為子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。門面模式又稱為外觀模式,它是一種對象結構型模式。
### 二、模式動機
現代的軟件系統都非常復雜,盡管我們已經想盡一切方法將其“分而治之”,把一個系統劃分為好幾個較小的子系統了,但是仍然可能會存在這樣的問題:子系統內有非常多的類,客戶端往往需要和許多對象打交道之后 才能完成想要完成的功能。
在我們的生活中醫院就是這樣的。一般的醫院都會分為掛號、門診、化驗、收費、取藥等。看病的病人要想治好自己的病(相當于一個客戶端想要實現自己的功能)就要和醫院的各個部門打交道。首先,病人需要掛號,然后門診,如果醫生要求化驗的話,病人就要去化驗,然后再回到門診室,最后拿藥,經過一系列復雜的過程后才能完成看病的過程。如下圖所示:

解決這種不便的方式就是引入門面模式。如果我們在醫院設立一個接待員的話,病人只負責和接待員接觸,由接待員負責與醫院的各個部門打交道,如下圖所示:

### 三、模式結構

從上圖中我們可以看出門面模式一共有兩種角色:
門面角色:客戶端調用這個角色的方法。此角色知曉相關的子系統的功能和責任。正常情況下,本角色會將所有從客戶端發來的請求委派到相應的子系統中去。
子系統角色:可以同時有一個或者多個子系統。每個子系統都不是一個單獨的類,而是一個類的集合。每一個子系統都可以被客戶端直接調用,或者被門面角色直接調用。子系統并不知道門面的存在,罪域子系統而言,門面僅僅是另一個客戶端而已。
### 四、實例分析
這次我們來關注一下土豪的個人生活,話說土豪下班回到家里后首先要做的就是把燈打開,我們假設他一共需要打開三個燈,然后就是打開熱水器燒水準備洗澡,在等待的過程還會打開電視機看新聞。如果我們用一般的方法來實現的話,代碼就會是下面這個樣子。
這是電燈的類,里邊有打開的方法。
~~~
package com.designpattern.facade;
public class Light {
public void open(){
System.out.println("Light has been opened!");
}
}
~~~
這是熱水器的類,里邊有打開的方法。
~~~
package com.designpattern.facade;
public class Heater {
public void open(){
System.out.println("Heater has been opened!");
}
}
~~~
這是電視機的類,里邊有打開的方法。
~~~
package com.designpattern.facade;
public class TV {
public void open(){
System.out.println("TV has been opened!");
}
}
~~~
在主函數里就要創建各種對象,并且調用他們的額open方法。我們看到主函數為了實現土豪下班回家這一個功能需要和三個電燈,一個熱水器和一臺電視機打交道,非常的復雜,所以這時候我們就應該使用門面模式。
~~~
package com.designpattern.facade;
public class Main {
public static void main(String[] args){
Light light1 = new Light();
Light light2 = new Light();
Light light3 = new Light();
Heater heater = new Heater();
TV tv = new TV();
/**
* 需要一步一步的操作
*/
light1.open();
light2.open();
light3.open();
heater.open();
tv.open();
}
}
~~~
在門面類中我們創建一個統一的open方法,來調度所有的開關。
~~~
package com.designpattern.facade;
public class Facade {
private Light light1, light2, light3;
private Heater heater;
private TV tv;
public Facade() {
light1 = new Light();
light2 = new Light();
light3 = new Light();
heater = new Heater();
tv = new TV();
}
public void open() {
light1.open();
light2.open();
light3.open();
heater.open();
tv.open();
}
}
~~~
這樣在主函數類只需要使用門面類就可以了。
~~~
package com.designpattern.facade;
public class Main2 {
public static void main(String[] args) {
Facade facade = new Facade();
/**
* 一步操作就可以完成所有的準備工作
*/
facade.open();
}
}
~~~
### 五、模式的優缺點
**優點:**
- 對客戶屏蔽子系統組件,減少了客戶處理的對象數目并使得子系統使用起來更加容易。通過引入門面模式,客戶代碼將變得很簡單,與之關聯的對象也很少。
- 實現了子系統與客戶之間的松耦合關系,這使得子系統的組件變化不會影響到調用它的客戶類,只需要調整外觀類即可。
- 降低了大型軟件系統中的編譯依賴性,并簡化了系統在不同平臺之間的移植過程,因為編譯一個子系統一般不需要編譯所有其他的子系統。一個子系統的修改對其他子系統沒有任何影響,而且子系統內部變化也不會影響到外觀對象。
- 只是提供了一個訪問子系統的統一入口,并不影響用戶直接使用子系統類。
**缺點:**
- 不能很好地限制客戶使用子系統類,如果對客戶訪問子系統類做太多的限制則減少了可變性和靈活性。
- 在不引入抽象外觀類的情況下,增加新的子系統可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。
### 五、使用場景
1、當要為一個復雜子系統提供一個簡單接口時可以使用外觀模式。該接口可以滿足大多數用戶的需求,而且用戶也可以越過外觀類直接訪問子系統。
2、客戶程序與多個子系統之間存在很大的依賴性。引入外觀類將子系統與客戶以及其他子系統解耦,可以提高子系統的獨立性和可移植性。
3、在層次化結構中,可以使用外觀模式定義系統中每一層的入口,層與層之間不直接產生聯系,而通過外觀類建立聯系,降低層之間的耦合度。
### 六、需要注意的幾點
**一個系統有多個外觀類**
在外觀模式中,通常只需要一個外觀類,并且此外觀類只有一個實例,換言之它是一個單例類。在很多情況下為了節約系統資源,一般將外觀類設計為單例類。當然這并不意味著在整個系統里只能有一個外觀類,在一個系統中可以設計多個外觀類,每個外觀類都負責和一些特定的子系統交互,向用戶提供相應的業務功能。
**不要試圖通過外觀類為子系統增加新行為**
不要通過繼承一個外觀類在子系統中加入新的行為,這種做法是錯誤的。外觀模式的用意是為子系統提供一個集中化和簡化的溝通渠道,而不是向子系統加入新的行為,新的行為的增加應該通過修改原有子系統類或增加新的子系統類來實現,不能通過外觀類來實現。
**外觀模式與迪米特法則**
外觀模式創造出一個外觀對象,將客戶端所涉及的屬于一個子系統的協作伙伴的數量減到最少,使得客戶端與子系統內部的對象的相互作用被外觀對象所取代。外觀類充當了客戶類與子系統類之間的“第三者”,降低了客戶類與子系統類之間的耦合度,外觀模式就是實現代碼重構以便達到“迪米特法則”要求的一個強有力的武器。
**抽象外觀類的引入**
外觀模式最大的缺點在于違背了“開閉原則”,當增加新的子系統或者移除子系統時需要修改外觀類,可以通過引入抽象外觀類在一定程度上解決該問題,客戶端針對抽象外觀類進行編程。對于新的業務需求,不修改原有外觀類,而對應增加一個新的具體外觀類,由新的具體外觀類來關聯新的子系統對象,同時通過修改配置文件來達到不修改源代碼并更換外觀類的目的。
源碼下載:[http://download.csdn.net/detail/xingjiarong/9308029](http://download.csdn.net/detail/xingjiarong/9308029)
- 前言
- 設計原則(一)"開-閉"原則(OCP)
- 設計原則(二)里氏替換原則(LSP)
- 設計原則(三)組合復用原則
- 設計原則(四)依賴倒置原則(DIP)
- 設計模式(一)簡單工廠模式
- 設計模式(二)工廠方法模式
- 設計模式(三)抽象工廠模式
- 設計模式(四)單例模式
- 設計模式(五)創建者模式(Builder)
- 設計模式(六)原型模式
- 設計模式(七)門面模式(Facade Pattern 外觀模式)
- 設計模式(八)橋梁模式(Bridge)
- 設計模式(九)裝飾模式(Decorator)
- 設計模式(十)適配器模式
- 設計模式(十一)策略模式
- 設計模式(十二)責任鏈模式
- 設計模式之UML(一)類圖以及類間關系(泛化 、實現、依賴、關聯、聚合、組合)
- 設計模式之橋梁模式和策略模式的區別