【TypeScript】デコレーターパターンの実装ガイド - 柔軟な機能拡張の実現
2024-11-10
2024-11-10
TypeScriptでのデコレーターパターンの実装ガイド
デコレーターパターンは、オブジェクトに動的に機能を追加するデザインパターンです。TypeScript
では、デコレーター構文を使用して、クラスやメソッド、プロパティに機能を拡張できます。この記事では、TypeScript
でのデコレーターパターンの実装方法と、さまざまなデコレーターの使い方について解説します。
デコレーターパターンの基本構造
デコレーターパターンは、以下の要素で構成されます:
- Component(基盤となるオブジェクト): 機能を装飾する対象となるクラスやオブジェクト。
- Decorator(装飾者): 基盤オブジェクトに新しい機能を追加するクラスや関数。
- ConcreteDecorator(具象デコレーター): Decoratorを実装し、具体的な拡張機能を提供します。
デコレーターパターンでは、装飾者が基盤オブジェクトをラップし、機能を拡張します。
TypeScript
では、デコレーター構文で装飾を簡潔に記述できるため、コードの再利用性が高まります。
TypeScriptのデコレーターの種類
TypeScript
では、以下の4種類のデコレーターを使って、クラスの各部分に機能を追加できます:
- クラスデコレーター: クラス全体に対して装飾します。
- メソッドデコレーター: 特定のメソッドに装飾を適用します。
- アクセサデコレーター: プロパティのゲッターやセッターに対して装飾を適用します。
- プロパティデコレーター: 特定のプロパティに装飾を適用します。
クラスデコレーターの実装
クラスデコレーターは、クラスの上に@DecoratorName
の形で記述し、クラス全体に機能を追加します。次の例では、@timestamped
デコレーターを使って、クラスのインスタンスが生成された時間を保持するようにしています。
// クラスデコレーター
function timestamped<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
timestamp = new Date();
};
}
// クラスにデコレーターを適用
@timestamped
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 使用例
const user = new User("Alice");
console.log(user.timestamp); // 出力: インスタンス生成時のタイムスタンプ
console.log(user.name); // 出力: Alice
この例では、@timestamped
デコレーターがクラスをラップし、生成時のタイムスタンプを保持するプロパティtimestamp
を追加しています。
メソッドデコレーターの実装
メソッドデコレーターは、特定のメソッドに装飾を適用し、メソッドの実行前後に処理を追加するのに役立ちます。以下の例では、メソッドの実行時間を計測する@logExecutionTime
デコレーターを定義しています。
// メソッドデコレーター
function logExecutionTime(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
): void {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`メソッド ${propertyKey} を実行します...`);
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`メソッド ${propertyKey} の実行時間: ${end - start}ms`);
return result;
};
}
// デコレーターをメソッドに適用
class Calculator {
@logExecutionTime
add(a: number, b: number): number {
return a + b;
}
}
// 使用例
const calculator = new Calculator();
calculator.add(5, 10);
// 出力: メソッド add を実行します...
// 出力: メソッド add の実行時間: xx ms
この例では、@logExecutionTime
デコレーターがadd
メソッドに実行時間のロギング機能を追加しています。
プロパティデコレーターの実装
プロパティデコレーターは、クラス内の特定のプロパティに対して装飾を適用します。以下の例では、特定のプロパティに@readonly
デコレーターを適用して、値の変更を防止しています。
// プロパティデコレーター
function readonly(target: any, propertyKey: string): void {
Object.defineProperty(target, propertyKey, {
writable: false
});
}
// プロパティにデコレーターを適用
class Person {
@readonly
name: string;
constructor(name: string) {
this.name = name;
}
}
// 使用例
const person = new Person("Alice");
console.log(person.name); // 出力: Alice
// プロパティはreadonlyなのでエラーが発生
// person.name = "Bob"; // エラー: Cannot assign to 'name' because it is a read-only property.
このコードでは、@readonly
デコレーターが適用されたname
プロパティが読み取り専用になり、インスタンス生成後に変更できなくなります。
複数デコレーターの組み合わせ
TypeScript
では、複数のデコレーターを組み合わせて、より高度な機能を実装することが可能です。次の例では、@logExecutionTime
と@logParameters
デコレーターを組み合わせて、メソッドの実行時間と引数のロギングを行っています。
// メソッド引数のロギングを行うデコレーター
function logParameters(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
): void {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`メソッド ${propertyKey} の引数:`, args);
return originalMethod.apply(this, args);
};
}
class AdvancedCalculator {
@logExecutionTime
@logParameters
multiply(a: number, b: number): number {
return a * b;
}
}
// 使用例
const advancedCalculator = new AdvancedCalculator();
advancedCalculator.multiply(5, 10);
// 出力: メソッド multiply の引数: [5, 10]
// 出力: メソッド multiply を実行します...
// 出力: メソッド multiply の実行時間: xx ms
このように、メソッドデコレーターを組み合わせることで、さまざまなロギング機能を同時に適用できます。
TypeScriptでのデコレーターパターンの利点
- コードの再利用性の向上
デコレーターを使用することで、特定の機能を必要な場所に簡単に適用できます。共通のロジックをデコレーターにまとめることで、コードの再利用がしやすくなります。 - 機能の追加が簡単
デコレーターパターンにより、既存のクラスやメソッドに新しい機能を追加する際、元のコードに手を加えずに機能を追加できます。新しい要件に応じて装飾するだけで柔軟に対応可能です。 - 柔軟な拡張性
デコレーターは、ログ、権限チェック、キャッシュ処理など、さまざまな機能を簡単に追加でき、各機能が他と分離されているため拡張性に優れています。
まとめ
TypeScript
のデコレーターパターンは、クラスやメソッド、プロパティに対して柔軟な機能拡張を実現する強力な手法です。デコレーターを使用することで、コードの再利用性が向上し、保守性が高く拡張性に優れたコードベースが実現できます。デコレーターパターンを活用して、柔軟な機能拡張ができる効率的なプログラム設計を目指しましょう。