如果你已經用過單例、觀察者、策略這些基礎設計模式(我們在〈從設計模式開始〉那篇聊過),那你可能會發現:當專案規模變大、模組之間的依賴越來越複雜,光靠四個基礎模式已經不夠用了。這篇文章要帶你再往上走一層,認識三個在前端大型架構裡非常好用的「進階設計模式」——裝飾器(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 硬幹」、但其實可以用責任鏈或代理收斂的邏輯是什麼?歡迎在留言區分享,我們一起把它重構得更優雅!