Decorator パターン実装
既存オブジェクトの振る舞いを動的に拡張し、継承を使わずに機能を追加するデザインパターン
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 | 認証ガード、ローディング表示 |
全体像を把握するには関連書籍も有用。
この記事は役に立ちましたか?
関連用語
デザインパターン
ソフトウェア設計で繰り返し現れる問題に対する再利用可能な解決策のカタログ
Proxy パターン
オブジェクトへのアクセスを代理オブジェクトが仲介し、アクセス制御やキャッシュなどの付加機能を提供するパターン
TypeScript
JavaScript に静的型付けを追加した言語で、大規模開発の安全性と生産性を向上させる
抽象クラス
直接インスタンス化できず、サブクラスに共通のインターフェースと部分的な実装を提供するクラス
Template Method パターン
アルゴリズムの骨格を親クラスで定義し、具体的なステップをサブクラスに委ねるデザインパターン
デコレータ
既存の関数やクラスに機能を追加するデザインパターンで、元のコードを変更せずに拡張する