從設計模式進階開始:用裝飾器、代理與責任鏈模式打磨大型前端架構實戰指南

如果你已經用過單例、觀察者、策略這些基礎設計模式(我們在〈從設計模式開始〉那篇聊過),那你可能會發現:當專案規模變大、模組之間的依賴越來越複雜,光靠四個基礎模式已經不夠用了。這篇文章要帶你再往上走一層,認識三個在前端大型架構裡非常好用的「進階設計模式」——裝飾器(Decorator)、代理(Proxy)、責任鏈(Chain of Responsibility),並搭配 TypeScript 實戰,讓你的程式碼更靈活、更容易維護。

一、為什麼基礎模式不夠用?

基礎模式解決的是「單一職責」與「物件建立」的問題;但當你要做的是「不改變原有程式碼,卻要動態加功能」、「控制物件存取」、「讓一連串處理器依序接力」這類需求時,就需要進階模式登場。它們的核心精神是:把「變化」與「穩定」分開,讓系統像樂高一樣可組合。

二、裝飾器模式(Decorator):不加鹽也能提味

生活化來說,裝飾器就像幫手機「貼膜、加殼、掛吊飾」——手機本體不變,但你一層層加上新功能。在前端,最常見的場景是:在不修改原函式的前提下,加上日誌、權限檢查、效能計時

步驟 1:先定義一個純粹的業務函式。

// 原本的業務邏輯:取得使用者資料
async function fetchUser(id: string): Promise<User> {
  const res = await api.get(`/users/${id}`);
  return res.data;
}

步驟 2:寫一個「裝飾器」函式,包住它並加上計時與錯誤兜底。

function withTiming<T extends (...args: any[]) => Promise<any>>(
  fn: T
): T {
  return (async (...args: Parameters<T>) => {
    const start = performance.now();
    try {
      return await fn(...args);
    } finally {
      console.log(`${fn.name} 耗時 ${performance.now() - start}ms`);
    }
  }) as T;
}

export const fetchUserTimed = withTiming(fetchUser);

重點:裝飾器不入侵原函式,可任意疊加(withTiming → withRetry → withCache),符合開閉原則。

三、代理模式(Proxy):幫物件把關的「守門員」

代理就像大樓的警衛:訪客(呼叫)不一定直接進到住戶家(真實物件),而是由警衛先檢查身分、決定要不要放行。在 Vben / Vue 專案裡,懶加載、介面快取、防抖節流都是代理模式的經典應用。

步驟 1:做一個「快取代理」,避免重複打 API。

class UserService {
  private cache = new Map<string, User>();
  private api = new RealUserApi();

  async getUser(id: string): Promise<User> {
    if (this.cache.has(id)) return this.cache.get(id)!; // 守門:命中快取直接回
    const user = await this.api.fetch(id);
    this.cache.set(id, user);
    return user;
  }
}

重點:代理與裝飾器常被搞混;關鍵差異是——代理「控制存取」(決定要不要、怎麼呼叫),裝飾器「增加行為」(原功能照跑)。

四、責任鏈模式(Chain of Responsibility):一棒接一棒

責任鏈像工廠的組裝流水線:每個工位處理自己負責的部分,處理完就交給下一棒。最常見的實戰是表單校驗、請求攔截器、權限中間件

步驟 1:定義處理器介面與鏈。

interface Handler {
  setNext(h: Handler): Handler;
  handle(ctx: FormCtx): string | null;
}

abstract class BaseHandler implements Handler {
  private next: Handler | null = null;
  setNext(h: Handler) { this.next = h; return h; }
  handle(ctx: FormCtx) {
    const err = this.check(ctx);
    if (err) return err;
    return this.next?.handle(ctx) ?? null;
  }
  abstract check(ctx: FormCtx): string | null;
}

步驟 2:實作具體校驗器並串起來。

class RequiredHandler extends BaseHandler {
  check(ctx: FormCtx) {
    return ctx.value ? null : `${ctx.field} 為必填`;
  }
}
class EmailHandler extends BaseHandler {
  check(ctx: FormCtx) {
    return /.+@.+\..+/.test(ctx.value) ? null : 'email 格式錯誤';
  }
}

const chain = new RequiredHandler();
chain.setNext(new EmailHandler());
const err = chain.handle({ field: 'email', value: '' });
// err === "email 為必填"

重點:責任鏈讓「校驗規則」可插拔、可擴充,新增一條規則只需掛上鏈,不必改動舊邏輯。

五、三個模式如何組合?

真實專案裡它們會一起出現:用代理快取 API 資料、用裝飾器為關鍵函式加日誌與重試、用責任鏈串起表單多層校驗。這三者在 CSF / Vben 這類中後台框架中特別常見,能顯著降低元件與業務邏輯之間的耦合。

  • Decorator:動態加功能,不動原程式碼
  • Proxy:控制存取,快取 / 防抖 / 懶加載
  • Chain of Responsibility:多處理器依序接力,規則可插拔

互動話題:

在你的專案裡,最常被你「手寫 if-else 硬幹」、但其實可以用責任鏈或代理收斂的邏輯是什麼?歡迎在留言區分享,我們一起把它重構得更優雅!