Facade Pattern(外觀模式)
是什麼?
Facade Pattern 為子系統中的一組複雜介面提供一個統一的高層介面。它定義了一個更高層次的介面,讓子系統更容易使用,而不改變子系統本身。
ℹ️GoF 分類
Facade 屬於結構型模式(Structural Pattern),重點在於如何組合類別和物件以形成更大的結構。
什麼時候用?
- 子系統有很多類別和方法,Client 只需要其中一部分常用功能
- 你想為複雜的第三方函式庫或 Legacy 系統提供簡化的 API
- 你想降低 Client 和子系統之間的耦合
什麼時候不該用?
⚠️過度設計警告
如果子系統本身已經很簡潔,再加一層 Facade 只會多一層不必要的間接呼叫。另外,Facade 不應該變成一個「什麼都做」的 God Class — 如果 Facade 的方法越來越多,代表你需要拆分成多個 Facade。
執行流程
辨識子系統
列出 Client 需要互動的所有子系統元件
找出常用操作
分析 Client 最常執行的操作流程
設計 Facade 介面
將常用操作封裝成簡潔的方法
實作 Facade
Facade 內部協調各子系統元件完成工作
Client 使用 Facade
Client 透過 Facade 操作,必要時仍可直接存取子系統
流程解讀:Facade 的設計從「分析 Client 的常用操作」開始,而非從子系統的能力出發。把 Client 最常執行的多步驟流程封裝成一個方法(如 PlaceOrder 封裝了庫存檢查 + 付款 + 出貨 + 通知)。Facade 本身不包含業務邏輯,只做「協調」— 按正確順序呼叫子系統、處理錯誤流程。Client 仍保有直接存取子系統的自由。
程式碼範例
C# 版本
// 子系統:庫存檢查
public class InventoryService
{
public bool IsInStock(string productId)
{
Console.WriteLine($"Checking inventory for {productId}...");
return true;
}
}
// 子系統:付款處理
public class PaymentService
{
public bool ProcessPayment(string orderId, decimal amount)
{
Console.WriteLine($"Processing payment ${amount} for order {orderId}...");
return true;
}
}
// 子系統:物流出貨
public class ShippingService
{
public string Ship(string orderId, string address)
{
Console.WriteLine($"Shipping order {orderId} to {address}...");
return $"TRACK-{orderId}-001";
}
}
// 子系統:通知
public class NotificationService
{
public void SendEmail(string to, string message)
{
Console.WriteLine($"Email to {to}: {message}");
}
}
// Facade:訂單處理
public class OrderFacade
{
private readonly InventoryService _inventory = new();
private readonly PaymentService _payment = new();
private readonly ShippingService _shipping = new();
private readonly NotificationService _notification = new();
public string PlaceOrder(string productId, decimal amount,
string address, string email)
{
if (!_inventory.IsInStock(productId))
throw new Exception("Product out of stock");
var orderId = Guid.NewGuid().ToString()[..8];
if (!_payment.ProcessPayment(orderId, amount))
throw new Exception("Payment failed");
var trackingId = _shipping.Ship(orderId, address);
_notification.SendEmail(email, $"Order {orderId} shipped! Tracking: {trackingId}");
return orderId;
}
}
// 使用:一行搞定整個下單流程
var facade = new OrderFacade();
var orderId = facade.PlaceOrder("PROD-001", 299.99m, "台北市信義區", "user@example.com");TypeScript 版本
// 子系統
class InventoryService {
isInStock(productId: string): boolean {
console.log(`Checking inventory for ${productId}...`);
return true;
}
}
class PaymentService {
processPayment(orderId: string, amount: number): boolean {
console.log(`Processing payment $${amount} for order ${orderId}...`);
return true;
}
}
class ShippingService {
ship(orderId: string, address: string): string {
console.log(`Shipping order ${orderId} to ${address}...`);
return `TRACK-${orderId}-001`;
}
}
class NotificationService {
sendEmail(to: string, message: string): void {
console.log(`Email to ${to}: ${message}`);
}
}
// Facade
class OrderFacade {
private inventory = new InventoryService();
private payment = new PaymentService();
private shipping = new ShippingService();
private notification = new NotificationService();
placeOrder(productId: string, amount: number,
address: string, email: string): string {
if (!this.inventory.isInStock(productId))
throw new Error("Product out of stock");
const orderId = crypto.randomUUID().slice(0, 8);
if (!this.payment.processPayment(orderId, amount))
throw new Error("Payment failed");
const trackingId = this.shipping.ship(orderId, address);
this.notification.sendEmail(email,
`Order ${orderId} shipped! Tracking: ${trackingId}`);
return orderId;
}
}
// 使用
const facade = new OrderFacade();
const orderId = facade.placeOrder("PROD-001", 299.99, "台北市信義區", "user@example.com");Python 版本
import uuid
# 子系統
class InventoryService:
def is_in_stock(self, product_id: str) -> bool:
print(f"Checking inventory for {product_id}...")
return True
class PaymentService:
def process_payment(self, order_id: str, amount: float) -> bool:
print(f"Processing payment ${amount} for order {order_id}...")
return True
class ShippingService:
def ship(self, order_id: str, address: str) -> str:
print(f"Shipping order {order_id} to {address}...")
return f"TRACK-{order_id}-001"
class NotificationService:
def send_email(self, to: str, message: str) -> None:
print(f"Email to {to}: {message}")
# Facade
class OrderFacade:
def __init__(self):
self._inventory = InventoryService()
self._payment = PaymentService()
self._shipping = ShippingService()
self._notification = NotificationService()
def place_order(self, product_id: str, amount: float,
address: str, email: str) -> str:
if not self._inventory.is_in_stock(product_id):
raise Exception("Product out of stock")
order_id = str(uuid.uuid4())[:8]
if not self._payment.process_payment(order_id, amount):
raise Exception("Payment failed")
tracking_id = self._shipping.ship(order_id, address)
self._notification.send_email(
email, f"Order {order_id} shipped! Tracking: {tracking_id}")
return order_id
# 使用
facade = OrderFacade()
order_id = facade.place_order("PROD-001", 299.99, "台北市信義區", "user@example.com")Java 版本
import java.util.UUID;
// 子系統
public class InventoryService {
public boolean isInStock(String productId) {
System.out.println("Checking inventory for " + productId + "...");
return true;
}
}
public class PaymentService {
public boolean processPayment(String orderId, double amount) {
System.out.println("Processing payment $" + amount + " for order " + orderId);
return true;
}
}
public class ShippingService {
public String ship(String orderId, String address) {
System.out.println("Shipping order " + orderId + " to " + address);
return "TRACK-" + orderId + "-001";
}
}
public class NotificationService {
public void sendEmail(String to, String message) {
System.out.println("Email to " + to + ": " + message);
}
}
// Facade
public class OrderFacade {
private InventoryService inventory = new InventoryService();
private PaymentService payment = new PaymentService();
private ShippingService shipping = new ShippingService();
private NotificationService notification = new NotificationService();
public String placeOrder(String productId, double amount,
String address, String email) {
if (!inventory.isInStock(productId))
throw new RuntimeException("Product out of stock");
String orderId = UUID.randomUUID().toString().substring(0, 8);
if (!payment.processPayment(orderId, amount))
throw new RuntimeException("Payment failed");
String trackingId = shipping.ship(orderId, address);
notification.sendEmail(email,
"Order " + orderId + " shipped! Tracking: " + trackingId);
return orderId;
}
}
// 使用
OrderFacade facade = new OrderFacade();
String orderId = facade.placeOrder("PROD-001", 299.99, "台北市信義區", "user@example.com");結構圖
結構解讀:Client 只呼叫 Facade 的一個方法(PlaceOrder),Facade 內部協調四個子系統:先檢查庫存、再處理付款、然後出貨、最後發通知。Client 不需要知道背後有幾個子系統、呼叫順序是什麼。重要的是 Facade 不會阻止 Client 直接存取子系統 — 如果 Client 需要更細緻的控制,仍可繞過 Facade 直接操作。
實戰補充
💡資深開發者經驗
Facade 是你每天都在寫但可能不自覺的模式。任何 Service Layer、SDK Wrapper、API Gateway 本質上都是 Facade。在微服務架構中,BFF(Backend For Frontend)就是 Facade — 前端只呼叫一個 endpoint,BFF 背後幫你協調多個微服務。設計 Facade 時要注意保持**「薄包裝」**:Facade 只做協調,不放業務邏輯。如果 Facade 的程式碼越寫越多,代表它承擔了太多責任,需要拆分。
理解測驗
🤔 Facade Pattern 的核心目的是什麼?
🤔 以下哪個不是 Facade Pattern 的特徵?
🤔 以下哪個場景最適合用 Facade Pattern?
面試常見問題
Q: Facade 和 Service Layer 有什麼關係?
A: Service Layer 本質上就是一組 Facade。在 DDD 中,Application Service 負責協調 Domain Service 和 Repository,對外提供簡化的 API — 這就是 Facade 的應用。差異在於 Service Layer 通常包含交易邊界(Transaction Boundary)和授權檢查,而純 Facade 只做協調轉發。
Q: Facade 什麼時候會變成反模式?
A: 當 Facade 變成 God Class(承擔過多責任,方法越加越多)時就是反模式。判斷標準:如果 Facade 的方法數量超過 10 個,或方法之間沒有邏輯關聯 → 需要拆分成多個 Facade,每個負責一個業務領域。
相關模式
| 模式 | 關係 | |------|------| | Adapter | 都是包裝層,但 Facade 簡化複雜的多子系統介面,Adapter 轉換單一不相容介面 | | Mediator | 都是協調者,但 Facade 是單向的(Client → 子系統),Mediator 是雙向的(同事之間互相協調) | | Singleton | Facade 常設計為 Singleton,因為通常只需要一個入口 | | Abstract Factory | Facade 可以用 Abstract Factory 來建立子系統的物件,進一步解耦 |
重點整理
💡一句話記住
Facade Pattern = 飯店櫃台:一個電話搞定所有事,但你也可以自己跑。 口訣:「簡化入口不鎖門,櫃台協調不做事」
| 概念 | 說明 | |------|------| | Facade(外觀) | 統一的高層介面,協調子系統完成工作 | | Subsystem(子系統) | 背後實際運作的多個類別 | | 核心好處 | 降低 Client 與子系統的耦合,簡化使用 | | Client 的自由 | Client 可以用 Facade,也可以直接使用子系統 | | 代價 | Facade 可能變成 God Class,需要注意職責控制 |