設計模式:責任鏈 (Chain of Responsibility)

8 分鐘
軟體設計設計模式OOP

Chain of Responsibility 讓請求沿著一條處理器鏈往下傳遞。每個處理器自行決定要把請求處理掉,或是繼續往下交給鏈上的下一個。


意圖與用途

一個請求需要經過多道檢查或處理步驟,但實際上會跑哪些步驟,取決於當下的情境與時機。

如果把這些邏輯全塞進同一個類別,就會造成高耦合。Chain of Responsibility 把每一個步驟抽離成獨立的處理器,讓你能自由組合它們。


結構與角色

  • Handler:定義「處理請求」與「設定下一個處理器」的介面
  • ConcreteHandler:實作處理邏輯,決定自己處理或往下傳遞
  • Client:組裝整條鏈,並送出請求

實作範例:HTTP 中間件鏈

TypeScript
interface Request {
  method: string;
  path: string;
  headers: Record;
  body?: unknown;
}

interface Response {
  status: number;
  message: string;
}

abstract class Middleware {
  protected next: Middleware | null = null;

  setNext(handler: Middleware): Middleware {
    this.next = handler;
    return handler; // 方便鏈式呼叫
  }

  protected proceed(request: Request): Response | null {
    return this.next ? this.next.handle(request) : null;
  }

  abstract handle(request: Request): Response | null;
}

// 驗證 Token
class AuthMiddleware extends Middleware {
  handle(request: Request): Response | null {
    const token = request.headers['authorization'];
    if (!token || !token.startsWith('Bearer ')) {
      return { status: 401, message: 'Unauthorized' };
    }
    console.log('[Auth] 通過驗證');
    return this.proceed(request);
  }
}

// 檢查 Content-Type
class ContentTypeMiddleware extends Middleware {
  handle(request: Request): Response | null {
    if (request.body && request.headers['content-type'] !== 'application/json') {
      return { status: 415, message: 'Unsupported Media Type' };
    }
    console.log('[ContentType] 通過檢查');
    return this.proceed(request);
  }
}

// 記錄請求日誌
class LoggingMiddleware extends Middleware {
  handle(request: Request): Response | null {
    console.log(`[Log] ${request.method} ${request.path}`);
    const response = this.proceed(request);
    console.log(`[Log] 回應: ${response?.status ?? '無'}`);
    return response;
  }
}

// 實際處理器
class RouteHandler extends Middleware {
  handle(request: Request): Response | null {
    return { status: 200, message: `處理 ${request.path}` };
  }
}

// 組裝中間件鏈
const logging = new LoggingMiddleware();
const auth = new AuthMiddleware();
const contentType = new ContentTypeMiddleware();
const route = new RouteHandler();

logging.setNext(auth).setNext(contentType).setNext(route);

// 送出請求
const response = logging.handle({
  method: 'POST',
  path: '/api/orders',
  headers: {
    authorization: 'Bearer abc123',
    'content-type': 'application/json',
  },
  body: { product: 'iPhone' },
});
console.log(response); // { status: 200, message: '處理 /api/orders' }

適用情境

適用時機

  • 多個物件都可能處理同一個請求,而具體由誰處理要到執行期才知道
  • 處理步驟需要彈性組合,或請求經過的順序很重要
  • HTTP 中間件、事件冒泡、審批流程

總結

Chain of Responsibility 把「請求由誰處理」這個決定藏了起來。送出端只需要把請求丟進鏈頭,它就會自然地流向正確的處理器。Express.js 的 middleware、NestJS 的 Guard/Interceptor,都是這個模式的實際應用。