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');
// → [2026-03-22T10:00:00.000Z] [INFO] Server started
Decorator は同じインターフェースを実装し、内部に別の実装を持つ。これにより、任意の順序で任意の数の Decorator を重ねられる。
継承との比較
継承で機能を追加すると、機能 N 個とロガー M 種の組み合わせで N×M 個のクラスが必要になる。Decorator パターンなら N+M 個のクラスで済み、実行時に自由に組み合わせられる。
// ❌ 継承: 組み合わせが爆発する
class TimestampConsoleLogger extends ConsoleLogger { /* ... */ }
class LevelConsoleLogger extends ConsoleLogger { /* ... */ }
class TimestampLevelConsoleLogger extends ConsoleLogger { /* ... */ }
class TimestampFileLogger extends FileLogger { /* ... */ }
// → 機能 N 個 × ロガー M 種 = N×M クラスが必要
// ✅ Decorator: 自由に組み合わせ
const logger = new TimestampLogger(new LevelLogger(new FileLogger()));
// → 機能 N 個 + ロガー M 種 = N+M クラスで済む
関数ベースの Decorator
TypeScript/JavaScript では、クラスを使わず高階関数で Decorator を実装する方が簡潔な場合が多い。
TypeScript 5.0 の Decorator 構文
TypeScript 5.0 で TC39 Stage 3 の Decorator 構文が導入された。
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 に静的型付けを追加した言語で、大規模開発の安全性と生産性を向上させる
抽象クラス
直接インスタンス化できず、サブクラスに共通のインターフェースと部分的な実装を提供するクラス
開放閉鎖の原則
SOLID の O - ソフトウェアの構成要素は拡張に対して開かれ、修正に対して閉じているべきという設計原則
ミドルウェア
リクエストとレスポンスの間に挟まる処理層で、認証・ログ・エラーハンドリングを横断的に適用する