Decorator パターン実装

既存オブジェクトの振る舞いを動的に拡張し、継承を使わずに機能を追加するデザインパターン

設計パターンTypeScript

Decorator パターンとは

Decorator パターンは、既存のオブジェクトをラップして振る舞いを動的に追加する GoF デザインパターンである。継承ではなくコンポジションで機能を拡張するため、機能の組み合わせが自由で、組み合わせの爆発を避けられる。

基本的な実装

基本的な実装のコード例を示す。

interface Logger {
  log(message: string): void;
}

class ConsoleLogger implements Logger {
  log(message: string) { console.log(message); }
}

// Decorator: タイムスタンプを追加
class TimestampLogger implements Logger {
  constructor(private inner: Logger) {}
  log(message: string) {
    this.inner.log(`[${new Date().toISOString()}] ${message}`);
  }
}

// Decorator: ログレベルを追加
class LevelLogger implements Logger {
  constructor(private inner: Logger, private level: string) {}
  log(message: string) {
    this.inner.log(`[${this.level}] ${message}`);
  }
}

// 組み合わせて使う (順序を変えると出力が変わる)
const logger = new TimestampLogger(new LevelLogger(new ConsoleLogger(), 'INFO'));
logger.log('Server started');
// → [INFO] [2026-03-22T10:00:00.000Z] Server started

Decorator は同じインターフェースを実装し、内部に別の実装を持つ。これにより、任意の順序で任意の数の Decorator を重ねられる。

前置の順序は直感と逆になりやすい。呼び出しは外側から内側へ進むので、外側の TimestampLogger が先にタイムスタンプを付け、その文字列を受け取った内側の LevelLogger がさらに前へ [INFO] を足す。結果として出力の先頭に立つのは内側の Decorator が付けた文字列になる。タイムスタンプを先頭に出したいなら new LevelLogger(new TimestampLogger(new ConsoleLogger()), 'INFO') と入れ子を逆にする。

継承との比較

継承で機能を追加すると、ロガー M 種それぞれに機能の組み合わせぶんのクラスが要る。機能 N 個を任意に組み合わせられるようにするなら組み合わせは 2 の N 乗から 1 を引いた通り数あり、クラス数は M × (2 の N 乗 - 1) に膨らむ。ロガー 1 種でも機能 2 個で 3 クラス、機能 4 個なら 15 クラスである。Decorator パターンなら N + M 個のクラスで済み、組み合わせは実行時に決められる。

// ❌ 継承: 組み合わせが爆発する
class TimestampConsoleLogger extends ConsoleLogger { /* ... */ }
class LevelConsoleLogger extends ConsoleLogger { /* ... */ }
class TimestampLevelConsoleLogger extends ConsoleLogger { /* ... */ }
class TimestampFileLogger extends FileLogger { /* ... */ }
// → ロガー M 種 × 機能の組み合わせ (2 の N 乗 - 1 通り) のクラスが必要

// ✅ Decorator: 自由に組み合わせ
const logger = new TimestampLogger(new LevelLogger(new FileLogger()));
// → 機能 N 個 + ロガー M 種 = N+M クラスで済む

関数ベースの Decorator

TypeScript/JavaScript では、クラスを使わず高階関数で Decorator を実装する方が簡潔な場合が多い。

TypeScript 5.0 の Decorator 構文

TypeScript 5.0 (2023 年 3 月) で、TC39 の Decorator 提案 (当時 Stage 3) に沿った構文が導入された。この標準構文はコンパイラフラグなしで書ける。

注意したいのは、以前から使えた --experimentalDecorators が古い提案をモデルにした別物だという点だ。デコレーター関数が受け取る引数の形が違うため、このフラグを有効にしたプロジェクトへ下のコードをそのまま持ち込んでも期待どおりには動かない。提案自体も 2026 年 8 月時点でまだ確定しておらず (TC39 のリポジトリ上の表示は Stage 2.7)、細部は版によって変わり得る。

function log(target: any, context: ClassMethodDecoratorContext) {
  return function (this: any, ...args: any[]) {
    console.log(`${String(context.name)} called with`, args);
    const result = target.apply(this, args);
    console.log(`${String(context.name)} returned`, result);
    return result;
  };
}

class OrderService {
  @log
  calculateTotal(items: Item[]): number {
    return items.reduce((sum, item) => sum + item.price, 0);
  }
}

実務での応用

実務での応用を以下にまとめる。

場面Decorator の例
Express ミドルウェア認証、ログ、CORSレート制限
AWS SDK ミドルウェアリトライ、ログ、メトリクス
Lambda ハンドラーエラーハンドリング、認証、バリデーション
React HOC認証ガード、ローディング表示

全体像を把握するには関連書籍も有用。

この記事は役に立ちましたか?

関連用語

関連する記事