當建置複雜的軟體系統時,僅寫出「可運作」的程式碼是不夠的。隨著系統規模擴大與需求演進,若物件建立方式結構不良、耦合過緊、控制流程寫死,以及靜態繼承層級僵化,都會導致架構脆弱不堪。

本指南完整拆解五大必備設計模式——抽象工廠策略轉接器觀察者裝飾器——並搭配真實企業情境、反模式、權衡取捨,以及逐步重構範例。


📊 軟體設計模式速查表

模式 類別 核心意圖 真實世界情境 主要優勢 架構權衡
抽象工廠 建立型 在不指定具體類別的情況下,建立相關物件家族。 多層級服務套件(例如:基礎版 vs. 精英版報稅方案)。 消除產品家族不相容;強制一致性物件建立。 介面過度繁衍與間接呼叫成本。
策略 行為型 將可互換的演算法封裝在統一介面之後。 多支付閘道路由器(Stripe、PayPal、Adyen)。 移除單體式 if-else/switch 區塊;遵循開閉原則。 簡單演算法變體導致類別/模組爆炸。
轉接器 結構型 在兩個不相容的介面之間進行轉譯。 整合舊有廠商 KYC SDK 的 onboarding 服務。 隔離領域邏輯,避免受外部/舊版 API 變動影響。 資料對應成本與潛在抽象洩漏。
觀察者 行為型 定義一對多依賴關係,將狀態變更廣播給訂閱者。 電商訂單結帳時的事件通知。 將領域核心與下游副作用解耦。 執行流程隱晦,增加除錯與追蹤難度。
裝飾器 結構型 在不改變類別結構的前提下,於執行時期動態新增行為。 含選擇性詐欺偵測與稽核的支付處理管線。 避免子類別排列爆炸;支援動態組合。 破壞物件識別(== 檢查失效);順序依賴風險。

1. 抽象工廠模式

核心概念

抽象工廠模式提供一個介面,用來建立一組相關或相依的物件家族,而無需指定其具體類別。它保證一起設計的產品集合能被一致地建立,並將建立邏輯與消費者程式碼解耦。

情境:分級報稅服務套件

假設某服務商提供兩種服務等級:

  1. 報稅服務:基礎版(標準 W-2)vs. 精英版(跨州、加密貨幣、股票選擇權)。
  2. 節稅服務:基礎版(401k 建議)vs. 精英版(遺產規劃與境外避稅)。

❌ 違反架構:分散建立與家族不相容風險

在商業邏輯中直接建立具體產品,容易因人為失誤導致開發者不小心把 BasicTaxFilingEliteTaxSaving 混用。

public class TaxServiceOrchestrator {

    public void executeService(String tier) {
        TaxFilingService filing;
        TaxSavingService saving;

        // Prone to human error: a developer could accidentally mix BASIC filing with ELITE saving
        if ("BASIC".equalsIgnoreCase(tier)) {
            filing = new BasicTaxFiling();
            saving = new BasicTaxSaving();
        } else if ("ELITE".equalsIgnoreCase(tier)) {
            filing = new EliteTaxFiling();
            saving = new EliteTaxSaving();
        } else {
            throw new IllegalArgumentException("Invalid service tier: " + tier);
        }

        filing.fileTaxes();
        saving.provideAdvice();
    }
}

Enter fullscreen mode Exit fullscreen mode


✅ 重構後架構:抽象工廠強制一致性

結構架構圖

graph TD
    subgraph Client Code
        Orchestrator[TaxServiceOrchestrator]
    end

    subgraph Factory Hierarchy
        FactoryInterface[FullServicePackageFactory Interface]
        BasicFactory[BasicPackageFactory]
        EliteFactory[ElitePackageFactory]

        FactoryInterface <|-- BasicFactory
        FactoryInterface <|-- EliteFactory
    end

    subgraph Product Families
        FilingInterface[TaxFilingService]
        SavingInterface[TaxSavingService]

        BasicFiling[BasicTaxFiling]
        EliteFiling[EliteTaxFiling]
        BasicSaving[BasicTaxSaving]
        EliteSaving[EliteTaxSaving]

        FilingInterface <|-- BasicFiling
        FilingInterface <|-- EliteFiling
        SavingInterface <|-- BasicSaving
        SavingInterface <|-- EliteSaving
    end

    Orchestrator --> FactoryInterface
    BasicFactory ..> BasicFiling
    BasicFactory ..> BasicSaving
    EliteFactory ..> EliteFiling
    EliteFactory ..> EliteSaving

Enter fullscreen mode Exit fullscreen mode

程式碼實作

// 1. Abstract Product 1: Tax Filing
public interface TaxFilingService {
    void fileTaxes();
}

// 2. Abstract Product 2: Tax Saving
public interface TaxSavingService {
    void provideAdvice();
}

// 3. Concrete Products for BASIC Tier
public class BasicTaxFiling implements TaxFilingService {
    @Override
    public void fileTaxes() { 
        System.out.println("Filing standard W-2 income taxes."); 
    }
}

public class BasicTaxSaving implements TaxSavingService {
    @Override
    public void provideAdvice() { 
        System.out.println("Providing basic 401(k) contribution advice."); 
    }
}

// 4. Concrete Products for ELITE Tier
public class EliteTaxFiling implements TaxFilingService {
    @Override
    public void fileTaxes() { 
        System.out.println("Filing multi-state, crypto, and stock-option taxes."); 
    }
}

public class EliteTaxSaving implements TaxSavingService {
    @Override
    public void provideAdvice() { 
        System.out.println("Providing estate planning and offshore tax shelter advice."); 
    }
}

// 5. The Abstract Factory Interface
public interface FullServicePackageFactory {
    TaxFilingService createFilingService();
    TaxSavingService createSavingService();
}

// 6. Concrete Factory 1: Basic Package Family
public class BasicPackageFactory implements FullServicePackageFactory {
    @Override
    public TaxFilingService createFilingService() { 
        return new BasicTaxFiling(); 
    }

    @Override
    public TaxSavingService createSavingService() { 
        return new BasicTaxSaving(); 
    }
}

// 7. Concrete Factory 2: Elite Package Family
public class ElitePackageFactory implements FullServicePackageFactory {
    @Override
    public TaxFilingService createFilingService() { 
        return new EliteTaxFiling(); 
    }

    @Override
    public TaxSavingService createSavingService() { 
        return new EliteTaxSaving(); 
    }
}

// 8. Clean Client Code (Guaranteed family cohesion, zero risk of mixing tiers)
public class TaxOrchestrator {
    private final TaxFilingService filingService;
    private final TaxSavingService savingService;

    // Injected with a cohesive factory family
    public TaxOrchestrator(FullServicePackageFactory packageFactory) {
        this.filingService = packageFactory.createFilingService();
        this.savingService = packageFactory.createSavingService();
    }

    public void runFullService() {
        filingService.fileTaxes();
        savingService.provideAdvice();
    }
}

Enter fullscreen mode Exit fullscreen mode

為什麼需要它?

  • 消除具體耦合:將商業邏輯與具體產品類別解耦。
  • 可擴展性:新增產品線/等級時,無需改動客戶端商業程式碼。
  • 防止產品混搭:強制由同一工廠建立的物件屬於同一產品家族。
  • 符合 SOLID:遵循開閉原則依賴反轉原則

架構權衡

  • 間接呼叫成本:開發者需穿越介面與工廠抽象層,才能檢視底層具體實例。
  • 介面過度繁衍:需為工廠及每個新產品線建立抽象介面。

何時應避免或拒絕

  • 簡單、不變物件:為簡單 POJO、DTO 或具標準建構子的領域實體建立工廠屬過度設計。
  • 使用 IoC/DI 框架:現代框架(如 Spring、NestJS、Wire)已作為全域 IoC 容器,為由 DI 容器管理的單例服務撰寫自訂抽象工廠是多餘的。

2. 策略模式

核心概念

策略模式將演算法選擇與演算法執行解耦。取代在核心商業邏輯中嵌入複雜條件判斷,它將每種演算法變體封裝在共同介面之後。

情境:多支付閘道路由器

支付路由系統需依使用者情境或地區,透過不同支付提供者(StripePayPalAdyen)處理交易。

❌ 違反架構:條件單體

public class PaymentProcessor {

    public PaymentResult processPayment(String gateway, BigDecimal amount, PaymentInfo info) {
        if ("STRIPE".equalsIgnoreCase(gateway)) {
            // 50 lines of Stripe SDK calls, header setup, and signature generation
            System.out.println("Processing via Stripe SDK...");
            return new PaymentResult(true, "ch_stripe_123");
        } else if ("PAYPAL".equalsIgnoreCase(gateway)) {
            // 60 lines of PayPal OAuth token fetching & payload mapping
            System.out.println("Processing via PayPal REST API...");
            return new PaymentResult(true, "pay_paypal_456");
        } else if ("ADYEN".equalsIgnoreCase(gateway)) {
            // 40 lines of Adyen HMAC signature generation & request building
            System.out.println("Processing via Adyen API...");
            return new PaymentResult(true, "adyen_tx_789");
        } else {
            throw new IllegalArgumentException("Unsupported payment gateway: " + gateway);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode


✅ 重構後架構:封裝策略登錄

graph LR
    Client[PaymentService] --> Registry[PaymentRegistry]
    Registry --> StrategyInterface[PaymentStrategy Interface]
    StrategyInterface <|.. Stripe[StripeStrategy]
    StrategyInterface <|.. PayPal[PayPalStrategy]
    StrategyInterface <|.. Adyen[AdyenStrategy]

Enter fullscreen mode Exit fullscreen mode

程式碼實作

// 1. Common Strategy Interface
public interface PaymentStrategy {
    void pay(double amount);
}

// 2. Concrete Strategy A
public class StripeStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        // Logic specific to Stripe API
        System.out.println("Paid $" + amount + " using Stripe.");
    }
}

// Concrete Strategy B
public class PayPalStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        // Logic specific to PayPal API
        System.out.println("Paid $" + amount + " using PayPal.");
    }
}

// 3. Simple Strategy Registry
public class PaymentRegistry {
    private final Map<String, PaymentStrategy> strategies = new HashMap<>();

    public PaymentRegistry() {
        // Register strategies by key
        strategies.put("STRIPE", new StripeStrategy());
        strategies.put("PAYPAL", new PayPalStrategy());
    }

    public PaymentStrategy get(String type) {
        PaymentStrategy strategy = strategies.get(type.toUpperCase());
        if (strategy == null) {
            throw new IllegalArgumentException("Invalid payment type: " + type);
        }
        return strategy;
    }
}

// 4. Client Code
public class PaymentService {
    private final PaymentRegistry registry = new PaymentRegistry();

    public void processPayment(String gatewayType, double amount) {
        // Fetch the matching strategy and execute it
        PaymentStrategy strategy = registry.get(gatewayType);
        strategy.pay(amount);
    }

    public static void main(String[] args) {
        PaymentService service = new PaymentService();
        service.processPayment("STRIPE", 100.0); // Output: Paid $100.0 using Stripe.
        service.processPayment("PAYPAL", 50.0);   // Output: Paid $50.0 using PayPal.
    }
}

Enter fullscreen mode Exit fullscreen mode

為什麼需要它?

  • 消除控制流膨脹:以查詢機制取代複雜的 if-elseswitch 區塊。
  • 隔離測試:可獨立單元測試個別演算法,而不需實例化周邊服務。
  • 符合開閉原則:新增整合(如 NetBankingStrategy)只需新增類別並註冊即可。

架構權衡

  • 類別/模組爆炸:以多個具體策略類別、登錄表與介面契約,換取單一多分支檔案。

何時應避免或拒絕

  • 不變/微不足道邏輯:若變體簡單且很少變更,標準條件判斷已足夠。
  • 函數式程式設計典範:在支援一級函式的語言(如 TypeScript、Rust、Go、現代 Java)中,為策略建立沉重類別階層是反模式,應優先傳遞 lambda 或高階函式。

3. 轉接器模式

核心概念

轉接器模式作為不相容介面之間的結構轉譯器。它允許核心領域契約保持乾淨,同時在第三方 API 或舊系統之間轉譯請求與回應。

情境:KYC 驗證整合

onboarding 服務需使用舊有外部廠商 SDK(LegacyPersonaSdk)進行第三方 KYC 身分驗證。

❌ 違反架構:領域受外部契約污染

// Legacy vendor class (Unmodifiable external SDK)
class LegacyPersonaSdk {
    public int executeCheck(String passportNum, String countryIso2) {
        // Returns 200 for pass, 401 for fail, 500 for error
        System.out.println("Calling Legacy Persona API...");
        return 200;
    }
}

// Violated Business Logic directly polluted with vendor-specific parameters and status codes
public class OnboardingService {
    private final LegacyPersonaSdk legacySdk = new LegacyPersonaSdk();

    public boolean verifyUser(String passportNumber, String countryCode) {
        // Direct coupling to legacy method signature and vendor status codes
        int responseCode = legacySdk.executeCheck(passportNumber, countryCode);
        return responseCode == 200;
    }
}

Enter fullscreen mode Exit fullscreen mode


✅ 重構後架構:領域轉接器隔離

graph LR
    Client[OnboardingService] --> TargetInterface[KycVerifier Interface]
    Adapter[LegacyPersonaAdapter] ..|> TargetInterface
    Adapter --> Adaptee[LegacyPersonaSdk]

Enter fullscreen mode Exit fullscreen mode

程式碼實作

// 1. Clean Application Target Contract
public interface KycVerifier {
    KycResult verifyUser(UserIdentity identity);
}

// Domain Models
record UserIdentity(String passportNumber, String countryCode) {}
record KycResult(boolean status, String referenceId) {}

// 2. Unmodifiable External API / Adaptee
class LegacyPersonaSdk {
    public int executeCheck(String passportNum, String countryIso2) {
        System.out.println("Calling Legacy Persona API...");
        return 200;
    }
}

// 3. Concrete Adapter Implementing Target Contract
public class LegacyPersonaAdapter implements KycVerifier {
    private final LegacyPersonaSdk legacySdk;

    public LegacyPersonaAdapter(LegacyPersonaSdk legacySdk) {
        this.legacySdk = legacySdk;
    }

    @Override
    public KycResult verifyUser(UserIdentity identity) {
        // Translate domain model to legacy SDK parameters
        int rawStatusCode = legacySdk.executeCheck(identity.passportNumber(), identity.countryCode());

        // Translate legacy response codes into clean domain response objects
        boolean passed = (rawStatusCode == 200);
        return new KycResult(passed, "PERSONA-TX-" + System.currentTimeMillis());
    }
}

// 4. Clean Business Logic (Completely decoupled from legacy vendor code)
public class OnboardingService {
    private final KycVerifier kycVerifier;

    // Injected via Interface
    public OnboardingService(KycVerifier kycVerifier) {
        this.kycVerifier = kycVerifier;
    }

    public boolean onboardUser(UserIdentity identity) {
        KycResult result = kycVerifier.verifyUser(identity);
        return result.status();
    }
}

Enter fullscreen mode Exit fullscreen mode

為什麼需要它?

  • 廠商中立:防止內部領域契約被外部廠商模型主導。
  • 因應變更的韌性:更換廠商只需撰寫新的轉接器實作;核心商業程式碼維持不變。

架構權衡

  • 資料對應成本:將內部模型轉譯為廠商特定參數,會在物件對應時增加少量 CPU 與記憶體開銷。
  • 抽象洩漏:若目標舊系統缺乏關鍵功能,轉接器可能需進行變通或隱藏缺失功能。

何時應避免或拒絕

  • 直接 1:1 對應介面:若轉接器僅透傳參數而無簽章或格式變更,則增加不必要的樣板程式碼。
  • 內部程式碼所有權:若呼叫端與接收端程式碼皆由你擁有與控制,應直接重構目標 API,而非包裝成轉接器。

4. 觀察者模式

核心概念

觀察者模式定義一對多發布-訂閱依賴關係。當核心主體狀態改變時,它會向已註冊的觀察者廣播事件通知,而無需知道觀察者的具體細節或執行機制。

情境:訂單結帳副作用

當電商應用程式中建立訂單時,需執行下游動作:寄送顧客電子郵件通知並更新倉庫庫存。

❌ 違反架構:寫死的副作用

public class OrderService {
    private final EmailService emailService = new EmailService();
    private final InventoryService inventoryService = new InventoryService();

    public void placeOrder(String orderId, String customerId, double amount) {
        // Core Business Transaction
        System.out.println("Order " + orderId + " saved to DB.");

        // BAD DESIGN: Core service explicitly knows and synchronously calls downstream tasks
        emailService.sendConfirmationEmail(customerId, orderId);
        inventoryService.deductStock(orderId);
    }
}

Enter fullscreen mode Exit fullscreen mode


✅ 重構後架構:解耦發布-訂閱系統

graph TD
    OrderService[OrderService] --> Publisher[OrderPublisher]
    Publisher --> ListenerInterface[OrderEventListener]
    ListenerInterface <|.. EmailListener[EmailNotificationListener]
    ListenerInterface <|.. InventoryListener[InventoryUpdateListener]

Enter fullscreen mode Exit fullscreen mode

程式碼實作

// 1. Immutable Event Payload
public record OrderPlacedEvent(String orderId, String customerId, double amount) {}

// 2. Observer (Subscriber) Interface
public interface OrderEventListener {
    void onOrderPlaced(OrderPlacedEvent event);
}

// 3. Concrete Observers (Decoupled Side-Effects)
public class EmailNotificationListener implements OrderEventListener {
    @Override
    public void onOrderPlaced(OrderPlacedEvent event) {
        System.out.println("Sending email to customer " + event.customerId() + " for order " + event.orderId());
    }
}

public class InventoryUpdateListener implements OrderEventListener {
    @Override
    public void onOrderPlaced(OrderPlacedEvent event) {
        System.out.println("Deducting inventory stock for order " + event.orderId());
    }
}

// 4. Subject / Publisher (Manages Subscribers & Broadcasts Events)
public class OrderPublisher {
    private final List<OrderEventListener> listeners = new ArrayList<>();

    public void registerListener(OrderEventListener listener) {
        listeners.add(listener);
    }

    public void publishOrderPlaced(OrderPlacedEvent event) {
        for (OrderEventListener listener : listeners) {
            try {
                listener.onOrderPlaced(event);
            } catch (Exception e) {
                // Isolate subscriber failures so they don't break other listeners
                System.err.println("Subscriber failed: " + e.getMessage());
            }
        }
    }
}

// 5. Clean Core Business Logic
public class OrderService {
    private final OrderPublisher publisher;

    public OrderService(OrderPublisher publisher) {
        this.publisher = publisher);
    }

    public void placeOrder(String orderId, String customerId, double amount) {
        // Core Business Logic
        System.out.println("Order " + orderId + " saved to DB.");

        // Clean event broadcast: OrderService has NO direct dependency on Email or Inventory services
        publisher.publishOrderPlaced(new OrderPlacedEvent(orderId, customerId, amount));
    }
}

Enter fullscreen mode Exit fullscreen mode

為什麼需要它?

  • 移除元件之間的緊密耦合。
  • 每個監聽器可分別建立與測試,且各自的失敗可被獨立處理,新監聽器的加入不會破壞既有流程。
  • 有助於遵循單一職責原則、開閉原則、依賴反轉原則。

架構權衡

  • 隱藏的執行鏈與除錯複雜度:由於訂閱者是動態註冊的,透過日誌檔案追蹤控制流程變得困難。你無法再簡單地在 IDE 中「前往定義」查看狀態改變後的所有後續行為。

何時應避免或拒絕

  • 若程序只有一個永久且不會改變的副作用,加入觀察者登錄表會造成不必要的間接呼叫。

擴展:從記憶體觀察者到分散式架構

在微服務架構中,記憶體觀察者會演進為分散式事件驅動架構(EDA)。服務不再使用記憶體登錄表,而是將訊息發布到如 Apache KafkaAWS SNS/SQSRabbitMQ 等分散式訊息代理。

為在不遺失事件的情況下維持系統一致性,正式架構常搭配交易式 Outbox 模式

  1. 商業資料與事件紀錄在同一個資料庫交易中寫入。
  2. 另一個中繼程序讀取 outbox 條目,並發布到訊息代理。
graph LR
    subgraph Microservice Context
        Service[Order Service] --> DB[(Database / Outbox Table)]
    end
    DB --> Relay[Debezium / Outbox Publisher]
    Relay --> Broker[Apache Kafka / RabbitMQ]
    Broker --> Listener1[Notification Service]
    Broker --> Listener2[Inventory Service]

Enter fullscreen mode Exit fullscreen mode


5. 裝飾器模式

核心概念

裝飾器模式允許在執行時期動態為物件新增職責,而無需修改其核心程式碼或引入靜態子類別爆炸。它以多層包裝核心元件,新增橫切關注點行為。

情境:信用卡處理選擇性附加功能

信用卡處理服務可於執行時期加入選擇性附加功能,例如詐欺檢查與稽核記錄。

❌ 違反架構:子類別排列混亂

為每種選擇性行為組合建立子類別,會導致類別數量呈指數成長。

graph TD
    A[PaymentProcessor Base] --> B[FraudCheckingPaymentProcessor]
    A --> C[AuditLoggingPaymentProcessor]
    A --> D[MetricsPaymentProcessor]
    B --> E[Fraud + Logging PaymentProcessor]
    C --> E
    C --> F[Logging + Metrics PaymentProcessor]
    D --> F
    B --> G[Fraud + Metrics PaymentProcessor]
    D --> G
    E --> H[Fraud + Logging + Metrics PaymentProcessor]
    F --> H
    G --> H

Enter fullscreen mode Exit fullscreen mode

public class BasicPaymentProcessor {
    public void processPayment(double amount) {
        System.out.println("Processing credit card payment of $" + amount);
    }
}

// Subclass permutations needed for every combination!
public class LoggingPaymentProcessor extends BasicPaymentProcessor { /* ... */ }
public class FraudCheckingPaymentProcessor extends BasicPaymentProcessor { /* ... */ }
public class LoggingAndFraudCheckingPaymentProcessor extends BasicPaymentProcessor { /* ... */ }
// Adding 3 more features leads to subclass explosion!

Enter fullscreen mode Exit fullscreen mode


✅ 重構後架構:動態組合鏈

graph LR
    Client[Client Code] --> FraudDecorator[FraudCheckDecorator]
    FraudDecorator --> AuditDecorator[AuditLoggingDecorator]
    AuditDecorator --> Core[StandardPaymentProcessor]

Enter fullscreen mode Exit fullscreen mode

程式碼實作

// 1. Component Interface
public interface PaymentProcessor {
    void processPayment(double amount);
}

// 2. Concrete Base Component (Core Domain Logic)
public class StandardPaymentProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Executing core payment debit of $" + amount);
    }
}

// 3. Abstract Decorator (Maintains reference to wrapped component)
public abstract class PaymentProcessorDecorator implements PaymentProcessor {
    protected final PaymentProcessor wrappedProcessor;

    public PaymentProcessorDecorator(PaymentProcessor wrappedProcessor) {
        this.wrappedProcessor = wrappedProcessor;
    }

    @Override
    public void processPayment(double amount) {
        wrappedProcessor.processPayment(amount); // Default delegation
    }
}

// 4. Concrete Decorator A
public class FraudCheckDecorator extends PaymentProcessorDecorator {
    public FraudCheckDecorator(PaymentProcessor wrappedProcessor) {
        super(wrappedProcessor);
    }

    @Override
    public void processPayment(double amount) {
        System.out.println("[FRAUD CHECK] Validating risk score for amount: $" + amount);
        super.processPayment(amount); // Delegate to next layer
    }
}

// Concrete Decorator B
public class AuditLoggingDecorator extends PaymentProcessorDecorator {
    public AuditLoggingDecorator(PaymentProcessor wrappedProcessor) {
        super(wrappedProcessor);
    }

    @Override
    public void processPayment(double amount) {
        System.out.println("[AUDIT LOG] Payment initiated at " + System.currentTimeMillis());
        super.processPayment(amount); // Delegate to next layer
        System.out.println("[AUDIT LOG] Payment execution finished.");
    }
}

// 5. Usage / Pipeline Construction
public class PaymentApplication {
    public static void main(String[] args) {
        // Base Processor
        PaymentProcessor processor = new StandardPaymentProcessor();

        // Dynamically compose processing pipeline at runtime: Fraud Check -> Audit Log -> Core Payment
        PaymentProcessor securePipeline = new FraudCheckDecorator(
            new AuditLoggingDecorator(processor)
        );

        securePipeline.processPayment(250.00);
    }
}

Enter fullscreen mode Exit fullscreen mode

為什麼需要它?

  • 防止類別爆炸:以動態組合取代靜態繼承組合。
  • 單一職責原則:橫切關注點(記錄、詐欺檢查、指標)被分離到個別包裝類別。

架構權衡

  • 身分危機:被裝飾的實例與內部包裝元件不共享物件識別(decoratedObject != innerObject)。相等性檢查(==)或基於反射的邏輯可能失效。
  • 順序開銷:若裝飾器依賴特定執行順序,巢狀組合可能引入細微的執行時期錯誤。

何時應避免或拒絕

  • 嚴格的順序依賴:若裝飾器 A 必須總是在裝飾器 B 之前執行,且順序錯亂會破壞系統狀態,則包裝管線會增加風險。
  • 框架已提供攔截器:現代企業框架提供面向切面程式設計(AOP)或中介軟體/過濾器管線(如 Spring AOP、Express Middlewares、gRPC Interceptors),可在不手動實例化物件包裝器的情況下,乾淨地處理橫切關注點。

💡 取得完整 10 頁系統設計指南

訂閱 The Tech Builder Newsletter,即可立即免費取得完整、未刪節的指南。

每週,訂閱者將收到:

  • 🎯 深入的生產環境事後檢討與系統設計權衡分析。
  • 🛠️ 針對資深 IC、技術主管與架構師的真實世界架構手冊。
  • 🎁 即時贈品:訂閱後立即取得完整 6 個月準備追蹤器與學習計畫10 頁系統設計速查表

👉 取得完整系統設計速查表