Facade Pattern(外觀模式)

是什麼?

Facade Pattern 為子系統中的一組複雜介面提供一個統一的高層介面。它定義了一個更高層次的介面,讓子系統更容易使用,而不改變子系統本身。

ℹ️GoF 分類

Facade 屬於結構型模式(Structural Pattern),重點在於如何組合類別和物件以形成更大的結構。

什麼時候用?

什麼時候不該用?

⚠️過度設計警告

如果子系統本身已經很簡潔,再加一層 Facade 只會多一層不必要的間接呼叫。另外,Facade 不應該變成一個「什麼都做」的 God Class — 如果 Facade 的方法越來越多,代表你需要拆分成多個 Facade。

執行流程

1

辨識子系統

列出 Client 需要互動的所有子系統元件

2

找出常用操作

分析 Client 最常執行的操作流程

3

設計 Facade 介面

將常用操作封裝成簡潔的方法

4

實作 Facade

Facade 內部協調各子系統元件完成工作

5

Client 使用 Facade

Client 透過 Facade 操作,必要時仍可直接存取子系統

流程解讀:Facade 的設計從「分析 Client 的常用操作」開始,而非從子系統的能力出發。把 Client 最常執行的多步驟流程封裝成一個方法(如 PlaceOrder 封裝了庫存檢查 + 付款 + 出貨 + 通知)。Facade 本身不包含業務邏輯,只做「協調」— 按正確順序呼叫子系統、處理錯誤流程。Client 仍保有直接存取子系統的自由。

程式碼範例

C# 版本

csharp
// 子系統:庫存檢查
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 版本

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 版本

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 版本

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
uses
Facade (OrderFacade)
coordinates
InventoryService
PaymentService
ShippingService
NotificationService

結構解讀: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,需要注意職責控制 |

你可能也想看

Decorator PatternFlyweight Pattern

按 ← → 鍵切換課程