摘要
本文介紹了外觀設計模式,它是一種結構型設計模式,通過引入一個外觀類來封裝復雜子系統的調用細節,對外提供簡單統一的接口。文中通過生活類比、關鍵角色介紹、使用場景分析以及結構說明等方面對這一模式進行了全面闡述,還涉及了實現方式、適合場景、實戰示例和相關思考,有助于讀者深入理解外觀設計模式的原理和應用。
1. 外觀設計模式定義
為復雜子系統提供一個統一的高層接口,使得子系統更易使用。外觀模式通過引入一個“外觀類(Facade)”,封裝內部子系統的調用細節,對外暴露一個簡單、統一的接口,隱藏系統的復雜性。
1.1. 📦 舉個通俗例子(生活類比):
點外賣 = 外觀模式:
你使用美團 App 點外賣,只用點菜、下單,不需要關心:
- 餐廳是否接單(子系統 A)
- 騎手怎么接單(子系統 B)
- 結算怎么走賬(子系統 C)
這就是“外觀類”統一封裝的行為。
1.2. ? 關鍵角色:
角色 | 說明 |
Facade(外觀) | 高層接口,封裝子系統的復雜邏輯,對外提供簡潔接口。 |
SubSystem(子系統) | 一組類或模塊,完成具體業務邏輯。外觀類內部會協調調用它們。 |
Client(客戶端) | 只依賴外觀類,屏蔽了對內部子系統的直接訪問 |
1.3. ? 使用場景:
- 系統結構復雜,希望對外提供簡化接口;
- 多個系統或模塊集成,希望統一接入方式;
- 用于分層架構(如 Controller -> Service -> Facade -> Subsystem);
- 舊系統封裝重構:用外觀封裝老接口,屏蔽調用細節。
2. 外觀設計模式結構
外觀模式包含如下角色:
- Facade: 外觀角色
- SubSystem:子系統角色
2.1. 外觀設計模式類圖
2.2. 外觀設計模式時序圖
3. 外觀設計模式實現方式
外觀設計模式(Facade Pattern)的實現方式非常清晰明確:通過封裝多個子系統的復雜調用邏輯,統一對外提供一個簡單接口。
3.1. ? 實現步驟(標準實現方式)
3.1.1. 🔹 步驟 1:定義多個子系統(SubSystem)類
@Service
public class RiskScoreService {public int getRiskScore(String userId) {System.out.println("計算用戶風險分...");return 75;}
}@Service
public class BlacklistService {public boolean isBlacklisted(String userId) {System.out.println("檢查黑名單...");return false;}
}@Service
public class CreditService {public int getCreditLimit(String userId) {System.out.println("獲取信用額度...");return 5000;}
}
3.1.2. 🔹 步驟 2:定義外觀類(Facade)
@Servcie
public class RiskFacade {@Autowiredprivate final RiskScoreService riskScoreService;@Autowiredprivate final BlacklistService blacklistService;@Autowiredprivate final CreditService creditService;public void assessUserRisk(String userId) {System.out.println("開始用戶風控評估...");if (blacklistService.isBlacklisted(userId)) {System.out.println("用戶被拉黑,拒絕服務!");return;}int score = riskScoreService.getRiskScore(userId);int credit = creditService.getCreditLimit(userId);System.out.println("風控評估完成,風險分:" + score + ",信用額度:" + credit);}
}
3.1.3. 🔹 步驟 3:客戶端只使用 Facade,不關心子系統
public class Client {public static void main(String[] args) {RiskFacade facade = new RiskFacade();facade.assessUserRisk("user123");}
}
3.2. ? 在 Spring 項目中的實現方式
如果項目使用 Spring 框架,我們通常會將子系統類標記為 @Service
,將外觀類作為一個統一入口暴露:
3.2.1. 🔹 子系統類(Spring Bean)
@Service
public class RiskScoreService { ... }@Service
public class BlacklistService { ... }@Service
public class CreditService { ... }
3.2.2. 🔹 外觀類作為統一接口
@Servcie
public class RiskFacade {@Autowiredprivate RiskScoreService riskScoreService;@Autowiredprivate BlacklistService blacklistService;@Autowiredprivate CreditService creditService;public void assess(String userId) {// 同上,統一調用子服務}
}
3.2.3. 🔹 Controller 層只依賴外觀類
@RestController
@RequestMapping("/risk")
public class RiskController {@Autowiredprivate RiskFacade riskFacade;@GetMapping("/assess")public String assess(@RequestParam String userId) {riskFacade.assess(userId);return "評估完成";}
}
3.3. ? 總結:外觀模式實現要點
步驟 | 內容 |
① | 把多個子系統服務拆分成獨立類 |
② | 建立一個 類,對外暴露統一方法 |
③ | 客戶端只與 類交互 |
④(Spring) | 把子系統類交給 Spring 管理,外觀類通過注入協調調用 |
4. 外觀設計模式適合場景
4.1. ? 適合使用外觀設計模式的場景
場景 | 說明 |
系統結構復雜 | 系統由多個子系統組成,接口調用復雜,客戶端需要簡化調用流程。 |
統一訪問入口 | 需要為多個子系統提供一個統一的接口,客戶端只需調用這個接口。 |
分層架構設計 | 在分層架構中,用外觀層屏蔽底層子系統的實現細節,降低耦合度。 |
系統重構與遷移 | 需要將舊系統接口封裝,兼容老舊代碼,逐步遷移到新系統。 |
多子系統協調 | 需要協調多個子系統的調用順序或組合調用邏輯,外觀類負責協調。 |
4.2. ? 不適合使用外觀設計模式的場景
場景 | 原因 |
系統簡單 | 業務流程簡單,接口調用不復雜,使用外觀反而增加額外層次和復雜度。 |
單一功能模塊 | 只涉及一個功能模塊,沒有必要額外封裝統一接口。 |
頻繁變動接口 | 子系統接口頻繁改變,外觀層也需頻繁修改,維護成本高。 |
業務高度耦合 | 業務流程需要客戶端靈活控制子系統內部調用,外觀隱藏細節不合適。 |
5. 外觀設計模式實戰示例
下面是一個金融風控場景下的外觀設計模式實戰示例,演示如何用Spring管理所有對象,并且使用注解方式注入,避免構造函數注入,方便集成和維護。
5.1. 項目背景
風控系統需要對用戶進行風險評估,涉及多個子系統服務:
- 黑名單查詢服務(BlacklistService)
- 風險分數計算服務(RiskScoreService)
- 信用額度服務(CreditService)
通過外觀模式(RiskFacade)統一暴露給業務調用層,隱藏各子系統復雜調用。
5.2. 子系統服務類
@Service
public class BlacklistService {public boolean isBlacklisted(String userId) {System.out.println("檢查用戶是否在黑名單中...");// 模擬黑名單檢查邏輯return "blacklistedUser".equals(userId);}
}@Service
public class RiskScoreService {public int calculateRiskScore(String userId) {System.out.println("計算用戶風險分數...");// 模擬風險分數計算return 80;}
}@Service
public class CreditService {public int getCreditLimit(String userId) {System.out.println("獲取用戶信用額度...");// 模擬信用額度查詢return 10000;}
}
5.3. 外觀類
@Component
public class RiskFacade {@Autowiredprivate BlacklistService blacklistService;@Autowiredprivate RiskScoreService riskScoreService;@Autowiredprivate CreditService creditService;public void assessUserRisk(String userId) {System.out.println("=== 開始風控評估 ===");if (blacklistService.isBlacklisted(userId)) {System.out.println("用戶 " + userId + " 在黑名單中,拒絕服務!");return;}int riskScore = riskScoreService.calculateRiskScore(userId);int creditLimit = creditService.getCreditLimit(userId);System.out.println("用戶 " + userId + " 風險分數: " + riskScore);System.out.println("用戶 " + userId + " 信用額度: " + creditLimit);System.out.println("=== 評估結束 ===");}
}
5.4. Controller 層示例
@RestController
@RequestMapping("/risk")
public class RiskController {@Autowiredprivate RiskFacade riskFacade;@GetMapping("/assess")public String assessRisk(@RequestParam String userId) {riskFacade.assessUserRisk(userId);return "風控評估完成";}
}
說明
- 所有類都由Spring管理,使用
@Service
和@Component
注解。 - 外觀類
RiskFacade
注入所有子系統服務,作為統一調用入口。 - 業務層(Controller)只調用外觀類接口,避免直接依賴多個子系統。
- 避免構造函數注入,使用
@Autowired
注解實現自動注入,符合你的要求。
6. 外觀設計模式思考
6.1. 門面設計設計模式和DDD中Facade設計區別
6.1.1. 門面設計模式(Facade Pattern)
- 定義:Facade 是一種結構型設計模式,用于為復雜的子系統提供一個簡化的統一接口。它屏蔽了系統的復雜性,客戶端通過門面類與子系統交互,而無需直接了解子系統的實現細節。
- 關注點:簡化接口,降低客戶端與子系統之間的耦合。
- 典型用途:
-
- 為一個復雜系統提供統一的入口。
- 隱藏子系統的內部復雜邏輯。
- 提高客戶端調用的便利性。
6.1.2. DDD 中的 Facade
- 定義:在領域驅動設計中,Facade 是一個用于協調多個領域對象或領域服務的接口或類。它通常用于應用層,作為應用服務的一部分,負責將客戶端的請求轉化為對領域層的調用。
- 關注點:隔離應用層與領域層,簡化應用層與外部系統(如 UI、接口調用等)的交互。
- 典型用途:
-
- 在應用層對外暴露接口。
- 封裝復雜的領域操作,協調多個領域對象和領域服務。
- 承載用例(Use Case)的實現邏輯。
6.1.3. 核心區別
維度 | 門面設計模式(Facade Pattern) | DDD 中的 Facade |
目的 | 為復雜子系統提供一個統一、簡化的接口,屏蔽系統內部實現細節。 | 為外部系統(如 UI 層、API 層)提供對領域層的調用接口。 |
適用范圍 | 用于封裝技術組件(子系統、模塊、服務)。 | 用于封裝領域邏輯,暴露領域行為。 |
位置 | 通常在技術實現層,用于協調多個技術模塊。 | 通常在應用層,調用領域層服務或聚合根。 |
關注點 | 簡化客戶端調用,隱藏子系統復雜性。 | 承載用例邏輯,協調領域對象和服務,實現業務需求。 |
是否直接操作領域 | 通常不直接操作領域對象,只封裝系統內部的模塊調用。 | 直接操作領域對象、聚合根、領域服務等。 |
6.2. 門面設計模式(Facade Pattern)的設計思想與 Application 層 的職責相似
6.2.1. Application層與門面模式(Facade Pattern)
Application 層(DDD 中的角色)
- 主要職責:
-
- 提供用例邏輯(Use Case)服務。
- 負責協調領域層(Domain Layer)的多個領域對象、領域服務和聚合根。
- 為外部系統(如 API 層、UI 層等)提供統一的調用接口。
- 不包含業務邏輯,業務邏輯屬于領域層,它僅負責調度領域邏輯。
- 核心思想:
-
- 簡化外部調用(UI 層或 API 層)對復雜領域邏輯的訪問。
- 將應用層與領域層隔離,保證領域層專注于業務規則,而應用層處理系統操作的組合與流程。
門面模式(Facade Pattern)
- 主要職責:
-
- 為子系統提供一個統一接口,屏蔽系統內部的復雜性。
- 將多個子系統或模塊的調用邏輯封裝在一個類中,外部調用方無需了解子系統的細節。
- 簡化客戶端調用,降低外部代碼與子系統的耦合度。
- 核心思想:
-
- 提供一個簡化的、高層次的接口來調用內部復雜的邏輯或子系統。
6.2.2. Application 層和門面模式的相似性
維度 | Application 層 | 門面模式(Facade Pattern) |
主要職責 | 調用領域層的對象和服務,為外部系統提供統一的調用接口。 | 調用子系統的服務,為客戶端提供簡化的調用入口。 |
目標 | 簡化外部系統對復雜領域邏輯的調用,協調領域服務和對象。 | 隱藏子系統的復雜性,為客戶端提供簡化的接口。 |
隱藏復雜性 | 隱藏領域層內部對象之間的交互細節。 | 隱藏子系統之間的交互細節。 |
調用方 | 外部系統(UI 層、API 層等)。 | 客戶端或其他模塊。 |
實現的粒度 | 以業務用例為單位,封裝一個完整的應用邏輯。 | 以技術組件為單位,封裝多個模塊或服務的調用邏輯。 |
結論:兩者都承擔了“簡化復雜性、統一接口”的職責,但 Application 層更專注于領域邏輯的編排和業務用例,而門面模式更關注技術子系統的整合和封裝。
6.3. 項目提供RPC 服務接口設計是不是屬于的門面設計模式?
是的,可以認為屬于門面設計模式的應用。門面模式(Facade Pattern)定義:為子系統中的一組復雜接口提供一個統一的高層接口,使子系統更易使用。
Spring 中 RPC 服務接口的特點(比如基于 Dubbo、gRPC、Spring Cloud):
特性 | 說明 |
📦 對外暴露服務接口 | Controller 不再是主角,RPC 接口才是系統“對外的門面” |
?? 封裝多個底層業務服務(Service、DAO、組件等) | 對外提供統一調用入口 |
🔐 對調用者隱藏實現細節 | 調用方只知道接口,內部邏輯對其不可見 |
🔌 提供遠程訪問能力 | 一般用于微服務、分布式系統間通信 |
這與門面模式的核心思想高度一致:對復雜子系統提供統一、簡潔、高層次的訪問接口。示例場景:
假設你有一個貸款審批系統,下游調用方只需調用如下 RPC 接口:
public interface LoanApprovalRpcService {ApprovalResult approveLoan(LoanApplicationDTO application);
}
而這個接口內部其實會:
- 校驗申請數據
- 調用風控系統
- 查詢信用評分
- 寫入審批日志
- 通知第三方平臺
這時你暴露的這個 RPC 接口就非常標準地扮演了“門面”角色:
- 對外隱藏了所有復雜的邏輯
- 調用者只需要關心一個方法:
approveLoan(...)
博文參考
- 4. 外觀模式 — Graphic Design Patterns
- 外觀設計模式(門面模式)