設計模式學習筆記(十三):外觀模式
1 概述
1.1 引言
根據單一權責原則,軟體中將一個系統劃分為若干個子系統有利於降低整個系統的複雜性,使客戶類與子系統之間的通訊和相互依賴關係達到最小,方法之一就是引入一個外觀角色,為子系統的訪問提供一個簡單而單一的入口。外觀模式通過引入一個新的外觀角色來降低原有系統的複雜度,同時降低客戶類與子系統類的耦合度。
(這裡的子系統是廣義的概念,可以是一個類,一個功能模組,系統的一個組成部分或者一個完整的系統)
如果沒有外觀角色,每個客戶端可能需要和多個子系統之間進行復雜的互動,系統的耦合度很大,簡化示意圖如下:
而引入外觀角色後,客戶端只需直接與外觀角色互動,客戶端與子系統之間的原有複雜度由外觀角色實現,從而降低系統耦合度,簡化示意圖如下:
外觀模式要求一個子系統的外部與其內部的通訊通過一個統一的外觀角色進行,外觀角色將客戶端與子系統的內部複雜性分隔開,使得客戶端只需要與外觀角色打交道,而不需要與子系統內部的很多物件打交道。
1.2 定義
外觀模式:外部與一個子系統的通訊通過一個統一的外觀角色進行,為子系統中的一組介面提供一個一致的入口。
外觀模式定義了一個高層介面,這個介面使得這一子系統更加容易使用。
外觀模式又叫門面模式,是一種物件結構型模式。
1.3 結構圖
1.4 角色
- Facade(外觀角色):在客戶端可以呼叫這個角色的方法,在外觀角色中可以知道相關的一個或多個子系統的功能和責任,正常情況下將來自客戶端的請求委派到對應的子系統中去,傳遞給相應的子系統物件處理
- SubSystem(子系統角色):每一個子系統是一個單獨的類,也可以是一個類的集合,實現子系統的功能。每一個子系統都可以被客戶端直接呼叫,或者被外觀角色呼叫,它處理由外觀類傳過來的請求,子系統並不知道外觀類的存在,對於子系統而已,外觀角色僅僅是另一個客戶端
2 典型實現
2.1 步驟
- 定義子系統:首先定義子系統,實現一個單一的功能,處理由客戶端傳來的請求
- 定義外觀角色:外觀角色可以知道一個或多個子系統的功能和責任,將來自客戶端的請求委派到對應的子系統去,外觀角色對於子系統而言是另一個客戶端
2.2 外觀角色
通常實現如下:
class Facade { private SubSystemA subSystemA = new SubSystemA(); private SubSystemB subSystemB = new SubSystemB(); private SubSystemC subSystemC = new SubSystemC(); public void method() { subSystemA.method(); subSystemB.method(); subSystemC.method(); } } class SubSystemA { public void method() { System.out.println("子系統A"); } } class SubSystemB { public void method() { System.out.println("子系統B"); } } class SubSystemC { public void method() { System.out.println("子系統C"); } }
3 例項
設計一個檔案加密系統,加密流程包括三部分:讀取檔案,加密檔案,儲存檔案。這三個操作相對獨立,並且封裝在三個不同的類中,使用外觀模式設計該系統。
子系統類:
FileReader
:檔案讀取類Encrypt
:檔案加密類FileWriter
:檔案儲存類
外觀角色類:Facade
。
程式碼如下:
public class Test
{
public static void main(String[] args) {
Facade facade = new Facade();
facade.fileEncrypt("111");
}
}
class FileReader
{
public void read(String name)
{
System.out.println("讀取檔案"+name);
}
}
class Encrypt
{
public void encrypt(String name)
{
System.out.println("加密檔案"+name);
}
}
class FileWriter
{
public void write(String name)
{
System.out.println("儲存檔案"+name);
}
}
class Facade
{
private FileReader reader = new FileReader();
private Encrypt encrypt = new Encrypt();
private FileWriter writer = new FileWriter();
public void fileEncrypt(String name)
{
reader.read(name);
encrypt.encrypt(name);
writer.write(name);
}
}
這裡的例子比較簡單,其實就是將讀取,加密以及儲存操作用外觀角色包裝起來,方便客戶端呼叫。
4 引入抽象外觀類
4.1 為什麼需要引入抽象外觀類?
在標準的外觀模式結構中,如果需要增加,刪除或修改外觀類互動的子系統類,必須修改外觀類或客戶端的原始碼,這將違背開閉原則,比如上面的例子中需要更換一種加密方法,也就是換一個加密類,這樣需要直接修改外觀類。
可以通過引入抽象外觀類來解決該問題,引入後,客戶端可以針對抽象外觀類進行程式設計,對於新的業務需求不需要修改原有的外
觀類,只需要新增一個對應的具體外觀類即可。
4.2 如何引入?
首先定義抽象外觀類,接著具體外觀類繼承或者實線抽象外觀類即可。客戶端針對抽象外觀類進行程式設計,在執行時再確定具體的外觀類,比如在上面例子的基礎上修改加密方法,首先定義抽象外觀類(這裡是介面,只有一個加密方法):
interface AbstractFacade
{
void encrypt(String name);
}
接著定義具體外觀類:
class Facade1 implements AbstractFacade
{
private FileReader reader = new FileReader();
private Encrypt1 encrypt1 = new Encrypt1();
private FileWriter writer = new FileWriter();
@Override
public void encrypt(String name)
{
reader.read(name);
encrypt1.encrypt(name);
writer.write(name);
}
}
class Facade2 implements AbstractFacade
{
private FileReader reader = new FileReader();
private Encrypt2 encrypt2 = new Encrypt2();
private FileWriter writer = new FileWriter();
@Override
public void encrypt(String name)
{
reader.read(name);
encrypt2.encrypt(name);
writer.write(name);
}
}
這兩個類除了加密方法不一樣其他都一樣,測試:
AbstractFacade facade = new Facade1();
facade.encrypt("111");
facade = new Facade2();
facade.encrypt("222");
引入抽象外觀類後,客戶端針對抽象外觀類進行程式設計,執行時確定具體外觀類,輸出如下:
5 注意事項
- 外觀單例:很多情況下為了節約系統資源,系統只需要一個外觀類的例項,也就是外觀類可以是一個單例類,這樣可以降低系統資源的消耗
- 多個外觀類:在一個系統中可以設計多個外觀類,每個外觀類負責和一些特定子物件互動,向客戶端提供相應業務功能
- 不要通過外觀類增加新行為:外觀模式的意圖是為子系統提供一個集中簡化的溝通渠道,而不是向子系統中增加新行為,新行為的增加應該通過修改原有子系統類或增加新的子系統類來實現而不是通過外觀類實現
6 主要優點
- 簡化處理:對客戶端遮蔽了子系統元件,減少了客戶端所需處理的物件數目並使得子系統使用起來更加容易,引入外觀模式後客戶端程式碼將簡化
- 鬆耦合:實現了子系統於客戶端之間鬆耦合關係,使得子系統的變化不會影響到客戶端,只需修改外觀類
- 子系統修改靈活:一個子系統的修改對其他子系統沒有影響,而且子系統內部變化也不會影響外觀物件
- 唯一入口:只提供了一個訪問子系統的唯一入口,但不會影響客戶端直接使用子系統類
7 主要缺點
- 不能限制客戶端使用子系統:外觀模式不能很好地限制客戶端直接使用子系統,如果客戶端對訪問子系統做太多的限制就會減少可變性與靈活性
- 可能需要修改外觀類:如果設計不當,增加新的子系統可能需要外觀類,違背OCP
8 適用場景
- 當要為訪問一系列複雜的子系統提供一個簡單的入口時
- 客戶端與多個子系統存在很大依賴性
- 層次化結構中,可以使用外觀模式定義系統中每一層的入口,層與層之間不直接產生聯絡,而通過外觀類建立聯絡,降低層之間的耦合度