外觀模式是一種使用頻率非常高的結構型設計模式, 它通過引入一個外觀角色來簡化客戶端和子系統之間的互動, 為複雜的子系統提供統一的入口, 降低子系統與客戶端的耦合度, 且客戶端呼叫非常方便.
1. 外觀模式概述
在軟體開發中, 有時候為了完成一項較為複雜的功能, 一個客戶類需要和多個業務類互動, 而這些需要互動的業務類經常作為一個整體出現, 由於涉及到的類比較多, 導致使用時候程式碼較為複雜, 此時特別需要一個類似服務員一樣的角色, 由他來負責和多個業務類進行互動, 而客戶端只需要與該類互動. 外觀模式中, 引入一個新的外觀類(Facade)來實現該功能, 外觀類充當軟體系統中的"服務員", 它為多個業務類的呼叫提供一個統一的入口, 簡化了類與類之間的互動. 在外觀模式中, 那些需要互動的業務類被稱為子系統(SubsSystem). 如果沒有外觀類,那麼每個客戶類需要和多個子系統之間進行復雜的互動,系統的耦合度將很大;而引入外觀類之後,客戶類只需要直接與外觀類互動,客戶類與子系統之間原有的複雜引用關係由外觀類來實現,從而降低了系統的耦合度。
在外觀模式中, 一個子系統的外部與其內部的通訊通過一個統一的外觀類進行, 外觀類將客戶類和子系統的內部的複雜性分隔開, 使得客戶類只需與外觀角色打交道, 而不需要與子系統內部的很多物件打交道.
外觀模式: 為子系統中的一組介面提供統一的入口. 外觀模式定義一個高層介面, 這個介面使得這一子系統更加容易被使用.
外觀模式又稱為門面模式, 它是一種物件結構型模式. 外觀模式是迪米特法則的一種具體實現, 通過引入一個新的外觀角色可以降低原有系統的複雜度, 同時降低客戶類與子系統的耦合度.
2. 外觀模式的實現
例一:
給自己舉個最熟悉的例子: MJRefresh, 其中 UIScrollView+MJRefresh 就是一個外觀類. 類丟擲的方法和屬性如下所示, 還是蠻簡單的, 使用時候, 我們只需要和這個外觀類進行互動, 將自己需要的header或者footer注入到外觀類中, 到底什麼時候用到這些物件什麼時候會呼叫待header的相關方法, 就不是我們關係的事情了, 這些複雜的邏輯都被放在外觀類中進行處理,.
這樣, 使用框架的程式設計師們和封裝框架的人之間通過MJRefresh這個擴充套件聯絡起來, 我們不需要關心MJRefresh內部實現多麼複雜, 也不必關心它的內部邏輯, 可以通過外觀類簡單的使用MJRefresh. 這就是外觀模式.
class MJRefreshHeader, MJRefreshFooter;
@interface UIScrollView (MJRefresh)
/** 下拉重新整理控制元件 */
@property (strong, nonatomic) MJRefreshHeader *mj_header;
@property (strong, nonatomic) MJRefreshHeader *header MJRefreshDeprecated("使用mj_header");
/** 上拉重新整理控制元件 */
@property (strong, nonatomic) MJRefreshFooter *mj_footer;
@property (strong, nonatomic) MJRefreshFooter *footer MJRefreshDeprecated("使用mj_footer");
#pragma mark - other
- (NSInteger)mj_totalDataCount;
@property (copy, nonatomic) void (^mj_reloadDataBlock)(NSInteger totalDataCount);
@end
複製程式碼
例二:
比如一個軟體的檔案加密模組, 該軟體可以將檔案加密然後把儲存到一個新檔案中, 具體流程分為: 讀取檔案,加密, 儲存加密檔案. 為了讓程式碼獨立重用, 讓設計更加符合單一職責原則, 將這三個操作的業務程式碼封裝到三個不同的類中.
使用外觀類組合加密操作, 如下圖, 其中EncryptFacade是一個外觀類, 加密操作分為三個操作, 但是我們在使用加密時候不必關心加密的過程, 只需要呼叫 FileEncrypt方法即可完成讀取/加密/寫入新檔案.
3. 抽象外觀類
如上例二途中所示, 標準的外觀模式中, 如果要更換外觀類的子系統, 則必須修改外觀類的客戶端原始碼, 這將違背開閉原則. 因此可以引進抽象外觀類來對系統進行改進, 在一定程度上可以解決該問題. 引入抽象外觀類之後, 客戶端可以針對抽象外觀類進行程式設計, 對於新的業務需求可以在不修改原有外觀類的情況下修改具體的實現類. 這樣可以適配不同的加密演算法.
4. 外觀模式效果與適用場景
外觀模式是一種使用頻率非常高的設計模式,它通過引入一個外觀角色來簡化客戶端與子系統之間的互動,為複雜的子系統呼叫提供一個統一的入口,使子系統與客戶端的耦合度降低,且客戶端呼叫非常方便。外觀模式並不給系統增加任何新功能,它僅僅是簡化呼叫介面。在幾乎所有的軟體中都能夠找到外觀模式的應用,如絕大多數B/S系統都有一個首頁或者導航頁面,大部分C/S系統都提供了選單或者工具欄,在這裡,首頁和導航頁面就是B/S系統的外觀角色,而選單和工具欄就是C/S系統的外觀角色,通過它們使用者可以快速訪問子系統,降低了系統的複雜程度。所有涉及到與多個業務物件互動的場景都可以考慮使用外觀模式進行重構。
4.1 模式優點
外觀模式的主要優點如下:
-
(1) 它對客戶端遮蔽了子系統元件,減少了客戶端所需處理的物件數目,並使得子系統使用起來更加容易。通過引入外觀模式,客戶端程式碼將變得很簡單,與之關聯的物件也很少。
-
(2) 它實現了子系統與客戶端之間的鬆耦合關係,這使得子系統的變化不會影響到呼叫它的客戶端,只需要調整外觀類即可。
-
(3) 一個子系統的修改對其他子系統沒有任何影響,而且子系統內部變化也不會影響到外觀物件。
4.2 模式缺點
外觀模式的主要缺點如下:
- (1) 不能很好地限制客戶端直接使用子系統類,如果對客戶端訪問子系統類做太多的限制則減少了可變性和靈活 性。
- (2) 如果設計不當,增加新的子系統可能需要修改外觀類的原始碼,違背了開閉原則。
4.3 模式適用場景
在以下情況下可以考慮使用外觀模式:
-
(1) 當要為訪問一系列複雜的子系統提供一個簡單入口時可以使用外觀模式。
-
(2) 客戶端程式與多個子系統之間存在很大的依賴性。引入外觀類可以將子系統與客戶端解耦,從而提高子系統的獨立性和可移植性。
-
(3) 在層次化結構中,可以使用外觀模式定義系統中每一層的入口,層與層之間不直接產生聯絡,而通過外觀類建立聯絡,降低層之間的耦合度。
角色:
1,外觀(Facade)角色 :客戶端可以呼叫這個角色的方法。此角色知曉相關子系統的功能和責任。在正常情況下,本角色會將所有從客戶端發來的請求委派到相應的子系統去。
2,子系統(SubSystem)角色 :可以同時有一個或者多個子系統。每個子系統都不是一個單獨的類,而是一個類的集合。每個子系統都可以被客戶端直接呼叫,或者被外觀角色呼叫。子系統並不知道外觀角色的存在,對於子系統而言,外觀角色僅僅是另外一個客戶端而已。
示例:
1,子系統角色,由若干個類組成
public class SubClass1 {
public void method1(){
System.out.println("這是子系統類1中的方法1");
}
public void method2(){
System.out.println("這是子系統類1中的方法2");
}
}
public class SubClass2 {
public void method1(){
System.out.println("這是子系統類2中的方法1");
}
public void method2(){
System.out.println("這是子系統類2中的方法2");
}
}
public class SubClass3 {
public void method1(){
System.out.println("這是子系統類3中的方法1");
}
public void method2(){
System.out.println("這是子系統類3中的方法2");
}
}
複製程式碼
2,外觀角色類
public class FacadeClass {
public void FacadeMethod(){
SubClass1 s1 = new SubClass1();
s1.method1();
SubClass2 s2 = new SubClass2();
s2.method1();
SubClass3 s3 = new SubClass3();
s3.method1();
}
}
複製程式碼
3,客戶端測試方法
public class ClientClass {
public static void main(String[] args) {
FacadeClass fc = new FacadeClass();
fc.FacadeMethod();
}
}
複製程式碼
Facade類其實相當於子系統中SubClass類的外觀介面,有了這個Facade類,那麼客戶端就不需要親自呼叫子系統中的那些具體實現的子類了,也不需要知道系統內部的實現細節,甚至都不需要知道這些子類的存在,客戶端只需要跟Facade類互動就好了,從而更好地實現了客戶端和子系統中具體類的解耦,讓客戶端更容易地使用系統。
同時,這樣定義一個Facade類可以有效地遮蔽內部的細節,免得客戶端去呼叫Module類時,發現一些不需要它知道的方法。如上程式碼,我的所有子類中的方法二都是方法一呼叫的方法,是配合方法一的,他們不需要被客戶端呼叫,而且具有一定的保密性,這樣使用外觀模式時就可以不被客戶端知道。
note:你總不能因為喝水的杯子摔壞了就再也不喝水了吧