TypeScriptのクラスデコレーターとメソッドデコレーターを組み合わせた高度な制御方法

TypeScriptのデコレーターは、クラスやメソッド、プロパティなどに対して機能を追加する柔軟な仕組みを提供します。これにより、コードの簡素化や再利用性が向上し、大規模プロジェクトでも効率的に管理が可能になります。特にクラスデコレーターとメソッドデコレーターは、オブジェクト指向プログラミングの強力なツールとなり、メソッドやクラスの振る舞いを簡単に制御できます。本記事では、この2つのデコレーターを組み合わせた高度な制御方法について、実例を交えながら詳しく解説します。

目次

TypeScriptのデコレーターとは

デコレーターは、クラスやクラスメンバー(メソッドやプロパティ)に対してアノテーションを追加し、その動作を拡張または変更する機能を提供します。デコレーターは、関数の形式で記述され、指定されたクラスやメソッドの上に適用されます。

デコレーターの基本的な仕組み

デコレーターは、特定の構文で関数を使用し、クラス、メソッド、プロパティ、アクセサ、パラメータに対して追加のロジックを注入することができます。デコレーターは、TypeScriptのコンパイル時に適用され、実行時にその効果が発揮されます。

デコレーターの使い方

TypeScriptでデコレーターを使うには、experimentalDecoratorsオプションをtsconfig.jsonファイルで有効にする必要があります。

{
  "compilerOptions": {
    "experimentalDecorators": true
  }
}

シンプルなデコレーターの例

以下は、簡単なクラスデコレーターの例です。デコレーターを使って、クラスの動作を変更できます。

function simpleDecorator(constructor: Function) {
  console.log("クラスがインスタンス化されました");
}

@simpleDecorator
class MyClass {
  constructor() {
    console.log("MyClassのコンストラクターが呼ばれました");
  }
}

const instance = new MyClass();

このコードでは、@simpleDecoratorがクラスに適用され、クラスのインスタンス化時にデコレーターが呼び出され、メッセージが表示されます。

クラスデコレーターの役割と実装例

クラスデコレーターは、クラス全体に対して新しい機能や振る舞いを追加するために使用されます。主にクラスの定義を変更したり、動的にプロパティを追加する際に利用されます。デコレーター関数はクラスのコンストラクタを引数として受け取り、そのコンストラクタの振る舞いを変更することが可能です。

クラスデコレーターの基本構造

クラスデコレーターは、以下のような関数として定義されます。デコレーター関数は、クラスのコンストラクタ関数を引数として受け取り、そのクラスに対してさまざまな操作を行うことができます。

function ClassDecorator(constructor: Function) {
  console.log(`${constructor.name}がデコレートされました`);
}

このデコレーターをクラスに適用するには、クラス宣言の前に@ClassDecoratorを記述します。

クラスデコレーターの実装例

次に、具体的な例として、クラスのプロパティを動的に追加するデコレーターを示します。このデコレーターは、インスタンスに新しいプロパティを追加し、そのプロパティを用いて特定の処理を実行します。

function AddTimestamp(constructor: Function) {
  return class extends constructor {
    timestamp = new Date();
  };
}

@AddTimestamp
class MyDecoratedClass {
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

const instance = new MyDecoratedClass("SampleClass");
console.log(instance.name); // Output: "SampleClass"
console.log(instance.timestamp); // Output: 現在の日付と時刻

この例では、@AddTimestampデコレーターがクラスに適用され、クラスインスタンスにtimestampという新しいプロパティが追加されています。このようにして、クラスデコレーターはクラス自体の動作を拡張するために利用されます。

クラスデコレーターの用途

クラスデコレーターは以下のようなシチュエーションで役立ちます。

  • ロギング機能の追加: クラスのインスタンス化やメソッドの呼び出しをログに残す。
  • メタデータの追加: クラスにメタデータを追加して、後でデータベースや外部サービスに関連付ける。
  • 動的プロパティの追加: 必要に応じてクラスのプロパティを動的に追加し、柔軟なオブジェクト構造を提供する。

クラスデコレーターは、コードの再利用性や保守性を高めるために非常に強力なツールとなります。

メソッドデコレーターの役割と実装例

メソッドデコレーターは、クラス内の特定のメソッドに対して機能を追加したり、その動作を変更するために使用されます。これにより、ログ出力、エラーハンドリング、アクセス制御など、メソッドレベルでの制御が可能になります。メソッドデコレーターは、メソッドの定義を強化したり、動的に振る舞いを変更する柔軟な手段を提供します。

メソッドデコレーターの基本構造

メソッドデコレーターは、以下のような関数形式で記述されます。デコレーター関数は、メソッドのターゲット(クラスのプロトタイプ)、メソッド名、メソッドのプロパティディスクリプターを引数として受け取ります。

function MethodDecorator(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  console.log(`${propertyKey} メソッドがデコレートされました`);
}

このデコレーターをメソッドに適用するには、メソッドの定義の上に@MethodDecoratorを記述します。

メソッドデコレーターの実装例

次に、メソッドデコレーターの実装例として、メソッドの実行前後にログを自動的に出力するデコレーターを示します。このデコレーターは、メソッドの実行をラップし、開始時と終了時にログを出力することで、メソッドの呼び出しをトレースします。

function LogExecutionTime(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`メソッド ${propertyKey} の実行開始`);
    const startTime = performance.now();

    const result = originalMethod.apply(this, args);

    const endTime = performance.now();
    console.log(`メソッド ${propertyKey} の実行終了 (${endTime - startTime} ms)`);

    return result;
  };

  return descriptor;
}

class MyClass {
  @LogExecutionTime
  executeTask() {
    // 処理が実行される部分
    console.log("タスクを実行中...");
  }
}

const instance = new MyClass();
instance.executeTask();

この例では、@LogExecutionTimeデコレーターをexecuteTaskメソッドに適用しています。デコレーターが適用されることで、メソッドの実行開始と終了のタイミングがログに記録され、実行時間も計測されます。

メソッドデコレーターの用途

メソッドデコレーターは、さまざまな場面で役立ちます。以下の用途が一般的です。

ログ出力

メソッドの呼び出しや実行状況を自動的にログに記録し、デバッグやパフォーマンス計測に役立てることができます。

アクセス制御

ユーザーの権限に基づいて、メソッドへのアクセスを制限するためにデコレーターを使用することが可能です。

エラーハンドリング

メソッド内で発生したエラーをキャッチし、共通のエラーハンドリングロジックを適用することもできます。

メソッドデコレーターを活用することで、コードのクリーンさを保ちながら、強力な機能を持たせることができるのです。

クラスデコレーターとメソッドデコレーターの違い

クラスデコレーターとメソッドデコレーターは、どちらもTypeScriptで機能を拡張するための強力なツールですが、適用される対象や用途が異なります。それぞれが提供する柔軟性と機能は、使用するシチュエーションによって変わるため、その違いを理解することが重要です。

クラスデコレーターの特徴

クラスデコレーターは、クラス全体に対して適用され、クラスの定義やインスタンス化のプロセスに影響を与えます。クラスデコレーターは、クラスのコンストラクタを変更したり、動的にプロパティやメソッドを追加することができます。

クラスデコレーターの主な用途

  • クラスレベルでのメタデータ追加: クラス自体に対して特定のメタデータを追加することで、他の部分で利用可能な情報を付加する。
  • インスタンスの拡張: クラスのインスタンスに動的にプロパティやメソッドを追加して、標準の振る舞いを拡張する。
  • 全体的な動作の変更: クラス全体に対して、特定のルールや制限を追加する(例:全インスタンスに共通の処理を追加)。

メソッドデコレーターの特徴

メソッドデコレーターは、クラス内の個々のメソッドに対して適用され、そのメソッドの動作を変更したり、メソッドの実行前後に処理を挿入するために使用されます。メソッドデコレーターは、個々のメソッドの動作に細かく影響を与える点で、クラスデコレーターとは異なります。

メソッドデコレーターの主な用途

  • 実行前後の処理追加: メソッドの実行前や後に特定の処理(例:ログ記録、タイミング計測)を自動的に追加する。
  • アクセス制御: 特定のユーザー権限や条件に基づいて、メソッドの実行を制御する。
  • エラーハンドリング: メソッド内で発生した例外をキャッチし、共通のエラーハンドリングロジックを適用する。

クラスデコレーターとメソッドデコレーターの使い分け

クラスデコレーターは、クラス全体の振る舞いを一括して制御する際に使用され、例えば、全インスタンスに共通のプロパティを持たせたり、ロギング機能を追加する際に適しています。一方、メソッドデコレーターは、特定のメソッドに対して局所的に制御を加えたい場合に使用され、ログの出力や実行時間の計測、エラーハンドリングなど、メソッドごとに個別の処理が必要な場合に適しています。

選択のポイント

  • クラス全体の制御が必要な場合は、クラスデコレーターを選択。
  • 特定のメソッドに対する追加機能が必要な場合は、メソッドデコレーターを使用。

これらの違いを理解して適切に使い分けることで、コードの保守性と柔軟性を向上させることができます。

クラスデコレーターとメソッドデコレーターの組み合わせ

クラスデコレーターとメソッドデコレーターを組み合わせることで、より高度で柔軟なコード制御が可能になります。クラス全体に適用されるルールをデコレーターで定義しつつ、個々のメソッドに特化した振る舞いを別のデコレーターで追加することで、効率的にコードの拡張や管理ができるようになります。

組み合わせのメリット

クラスデコレーターとメソッドデコレーターを組み合わせることにより、以下のようなメリットがあります。

コードの一貫性の向上

クラス全体に共通のルールやプロパティを設定しつつ、特定のメソッドにだけ異なる動作を追加できるため、一貫した振る舞いと柔軟な制御が両立できます。

可読性と保守性の向上

デコレーターを使ってクラスやメソッドに特定の機能を簡潔に付与することで、コードが簡潔かつ直感的になります。変更が必要な場合、デコレーターを調整するだけで簡単に振る舞いを変えることができ、保守性も向上します。

クラスデコレーターとメソッドデコレーターの実装例

次に、クラスデコレーターとメソッドデコレーターを組み合わせた具体的な例を示します。この例では、クラスデコレーターで全メソッドに共通の機能を追加しつつ、メソッドデコレーターで特定のメソッドに独自の機能を付加しています。

function ClassLogger(constructor: Function) {
  console.log(`クラス ${constructor.name} が作成されました`);
}

function MethodLogger(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`メソッド ${propertyKey} が呼び出されました`);
    return originalMethod.apply(this, args);
  };

  return descriptor;
}

@ClassLogger
class MyClass {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  @MethodLogger
  sayHello() {
    console.log(`こんにちは、${this.name}さん!`);
  }

  @MethodLogger
  sayGoodbye() {
    console.log(`さようなら、${this.name}さん!`);
  }
}

const instance = new MyClass("太郎");
instance.sayHello();
instance.sayGoodbye();

実装例の解説

この例では、以下のようにクラスデコレーターとメソッドデコレーターを組み合わせています。

  • @ClassLogger: クラスデコレーターで、クラスが作成された際にその名前をログに記録しています。これにより、クラスのインスタンス化時に自動的にログが出力されます。
  • @MethodLogger: メソッドデコレーターで、メソッドが呼び出された際にログを記録しています。各メソッドが呼び出された時点で、ログ出力が行われます。

これにより、クラス全体に共通の処理(クラス作成のログ出力)と、メソッドごとの処理(メソッド呼び出しのログ出力)がうまく連携しています。

クラスとメソッドの連携による高度な制御

クラスデコレーターとメソッドデコレーターの組み合わせは、さまざまなユースケースに対応できます。例えば、クラスデコレーターで認証や共通のデータ初期化を行い、メソッドデコレーターで個別のメソッドにロギングやエラーハンドリングを適用することが可能です。これにより、クラス全体の一貫性を保ちながら、柔軟で高度な制御を行うことができます。

実例:ログの自動化

クラスデコレーターとメソッドデコレーターを組み合わせて、ログ出力を自動化する実用的な例を紹介します。この方法は、クラスのインスタンス化やメソッドの呼び出し時に自動的にログを記録することで、トラブルシューティングやパフォーマンスモニタリングに役立ちます。手動でログを追加する必要がなくなり、コードの簡素化と保守性の向上に繋がります。

ログ自動化の基本的な仕組み

ログの自動化は、クラスデコレーターでクラスの生成を監視し、メソッドデコレーターでメソッドの実行をトレースすることで実現します。これにより、クラスがどのように動作しているかを簡単に追跡することが可能です。

ログ自動化の実装例

以下は、クラスのインスタンス化とメソッドの実行時に自動的にログを出力するデコレーターを組み合わせた実装例です。

function LogClassCreation(constructor: Function) {
  const originalConstructor = constructor;

  function newConstructor(...args: any[]) {
    console.log(`クラス ${constructor.name} がインスタンス化されました`);
    return new originalConstructor(...args);
  }

  newConstructor.prototype = originalConstructor.prototype;
  return newConstructor as any;
}

function LogMethodExecution(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`メソッド ${propertyKey} が実行されます`);
    const result = originalMethod.apply(this, args);
    console.log(`メソッド ${propertyKey} が終了しました`);
    return result;
  };

  return descriptor;
}

@LogClassCreation
class LoggerExample {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  @LogMethodExecution
  greet() {
    console.log(`こんにちは、${this.name}さん!`);
  }

  @LogMethodExecution
  farewell() {
    console.log(`さようなら、${this.name}さん!`);
  }
}

const instance = new LoggerExample("花子");
instance.greet();
instance.farewell();

実装例の解説

この実装では、クラスの生成とメソッドの実行が自動的にログに記録されます。

  • @LogClassCreation: クラスデコレーターでクラスのインスタンス化をトラッキングします。LoggerExampleクラスがインスタンス化される際に、コンソールにクラスが生成されたことを記録します。
  • @LogMethodExecution: メソッドデコレーターで、メソッドの実行前後にログを自動的に出力します。メソッドの開始と終了のタイミングがわかるため、メソッドの呼び出し状況を簡単にモニターできます。

この仕組みを使うことで、クラスとメソッドの動作をリアルタイムで監視し、バグの特定やパフォーマンス改善に活用できます。

ログ自動化の利点

  • トラブルシューティングの簡素化: クラスの生成やメソッドの呼び出しがログに記録されるため、実行状況を把握しやすく、問題発生時のデバッグが容易になります。
  • パフォーマンスモニタリング: メソッドの実行時間や頻度を追跡でき、パフォーマンスのボトルネックを特定する手助けとなります。
  • コードの簡素化: ログ出力コードを手動で挿入する必要がなくなり、コードが簡潔かつ読みやすくなります。

このように、クラスデコレーターとメソッドデコレーターを活用してログ出力を自動化することで、開発者の負担を軽減しつつ、システムの監視やデバッグを効率化することが可能です。

実例:権限管理

クラスデコレーターとメソッドデコレーターを組み合わせることで、アプリケーションにおける権限管理を効率的に実装することができます。特定のユーザーに対してメソッドの実行を制限したり、特定の条件を満たさなければメソッドを実行できないようにする仕組みをデコレーターで実現できます。

権限管理の基本的な仕組み

権限管理は、メソッドが呼び出される前に特定の条件をチェックすることで実現されます。たとえば、ユーザーが管理者権限を持っているかどうか、あるいはログイン状態であるかを確認し、条件を満たさない場合はメソッドの実行を拒否することができます。これをメソッドデコレーターで制御することにより、権限管理を柔軟に行うことができます。

権限管理の実装例

以下は、メソッドデコレーターを使って権限管理を実装する例です。この例では、特定のユーザーにのみアクセスを許可するように設定されています。

function AdminOnly(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    const user = this.user;  // クラスのプロパティとしてユーザー情報を保持
    if (user.role !== 'admin') {
      console.log('アクセスが拒否されました。管理者権限が必要です。');
      return;
    }

    return originalMethod.apply(this, args);
  };

  return descriptor;
}

class User {
  constructor(public name: string, public role: string) {}
}

class SecureOperations {
  user: User;

  constructor(user: User) {
    this.user = user;
  }

  @AdminOnly
  deleteSensitiveData() {
    console.log('機密データが削除されました。');
  }

  @AdminOnly
  accessRestrictedArea() {
    console.log('制限区域にアクセスしました。');
  }
}

// 管理者ユーザーの例
const adminUser = new User('管理者', 'admin');
const adminOperations = new SecureOperations(adminUser);
adminOperations.deleteSensitiveData();  // 実行される
adminOperations.accessRestrictedArea();  // 実行される

// 一般ユーザーの例
const regularUser = new User('一般ユーザー', 'user');
const userOperations = new SecureOperations(regularUser);
userOperations.deleteSensitiveData();  // アクセス拒否
userOperations.accessRestrictedArea();  // アクセス拒否

実装例の解説

この実装では、@AdminOnlyというメソッドデコレーターを使って、特定のメソッドに対するアクセスを管理者だけに制限しています。

  • @AdminOnly: メソッドの実行前にユーザーの権限をチェックします。ユーザーが管理者でない場合、メソッドの実行を拒否し、メッセージを表示します。
  • SecureOperationsクラス: ユーザーの権限に基づいて、メソッドの実行を制限します。deleteSensitiveDataaccessRestrictedAreaは、管理者のみが実行できるメソッドです。

権限管理の応用可能性

この権限管理の仕組みは、以下のようにさまざまな場面で応用可能です。

ユーザーごとのアクセス制限

ユーザーの役割やグループに基づいて、メソッドへのアクセスを制限できます。これにより、一般ユーザーには操作できない管理機能や、特定の操作を安全に保護できます。

ログイン状態の確認

メソッド実行前にログインしているかどうかを確認し、未ログイン状態ではメソッドの実行を拒否する機能を実装できます。

条件付き機能の有効化

システムが特定のモードや条件下にある場合のみ、メソッドを実行できるようにすることで、安全性や制御を高めることが可能です。

権限管理による安全性の向上

デコレーターを用いた権限管理は、コードを簡潔に保ちながら安全性を高めることができる優れた手法です。アプリケーション全体のセキュリティ要件に応じて、デコレーターを柔軟に活用することで、堅牢なシステムを構築できます。

デコレーターを使ったエラーハンドリング

エラーハンドリングは、アプリケーションの安定性を保つために非常に重要です。デコレーターを使うことで、個々のメソッドにエラーハンドリングのロジックを追加することなく、共通のエラーハンドリングを簡単に実装できます。これにより、重複したコードを減らし、メソッドごとに一貫したエラーハンドリングを行うことが可能になります。

エラーハンドリングの基本的な仕組み

デコレーターを使ったエラーハンドリングでは、メソッドの実行中にエラーが発生した場合、例外をキャッチして共通のエラーメッセージを出力したり、エラーに対する特定の処理を行うことができます。これにより、メソッドごとにtry-catch文を挿入する必要がなくなり、エラーハンドリングを一元化できます。

エラーハンドリングの実装例

以下の例では、メソッドデコレーターを使用して、メソッド内で発生するすべての例外をキャッチし、共通のエラーメッセージをログに出力するエラーハンドリングを実装しています。

function CatchErrors(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    try {
      return originalMethod.apply(this, args);
    } catch (error) {
      console.error(`エラーが発生しました: ${error.message}`);
    }
  };

  return descriptor;
}

class ErrorProneService {
  @CatchErrors
  riskyOperation() {
    console.log("危険な操作を実行中...");
    throw new Error("重大なエラーが発生しました");
  }

  @CatchErrors
  safeOperation() {
    console.log("安全な操作を実行中...");
  }
}

const service = new ErrorProneService();
service.riskyOperation();  // エラーがキャッチされ、エラーメッセージが表示される
service.safeOperation();   // 問題なく実行される

実装例の解説

この例では、@CatchErrorsというメソッドデコレーターを用いて、メソッド実行中に発生する例外を自動的にキャッチし、エラーメッセージを出力しています。

  • @CatchErrors: メソッドが実行される際にtry-catch文で囲み、例外が発生した場合は、エラーメッセージを出力します。このデコレーターを適用することで、エラーハンドリングを各メソッドで個別に実装する必要がなくなります。
  • riskyOperation: このメソッドはエラーを意図的に発生させますが、デコレーターによりエラーがキャッチされ、プログラムのクラッシュを防ぎます。
  • safeOperation: エラーが発生しないメソッドでも、デコレーターが適用されているため、統一されたエラーハンドリングの仕組みが機能しています。

デコレーターによるエラーハンドリングの利点

コードの簡素化

各メソッドで個別にtry-catchブロックを実装するのではなく、デコレーターによって共通のエラーハンドリングを提供できるため、コードが簡潔になります。

一貫性の確保

すべてのメソッドで同じ形式のエラーハンドリングが実行されるため、エラーの扱いに一貫性が保たれます。これにより、エラー処理のバグが減り、メンテナンスが容易になります。

再利用性の向上

デコレーターによるエラーハンドリングは、複数のメソッドやクラスに再利用可能です。必要に応じて異なるエラーハンドリングロジックをデコレーターとして実装することで、プロジェクト全体で効率的に利用できます。

エラーハンドリングの応用

このエラーハンドリングの仕組みは、さまざまなケースに適用可能です。

リトライ機能

エラーが発生した場合に、一定の回数リトライを行うロジックをデコレーター内で実装し、信頼性を向上させることができます。

通知機能

エラーが発生した際に、ログに記録するだけでなく、管理者に通知を送るなどのアクションをデコレーターに組み込むことも可能です。

デコレーターを使ったエラーハンドリングは、コードのメンテナンス性を向上させるとともに、アプリケーションの安定性を高めるための有効な手法です。これにより、エラーハンドリングを簡単かつ効率的に実装できます。

応用例:テストフレームワークへの適用

デコレーターは、テストフレームワークの開発にも応用することができます。テストコードを書く際に、デコレーターを活用してテストの前後処理を自動化したり、特定の条件でのみテストを実行するロジックを簡潔に追加できます。これにより、テストコードが簡素化され、テストの一貫性が保たれます。

テストフレームワークにおけるデコレーターの役割

テストフレームワークでは、以下のような役割でデコレーターが使用できます。

前後処理の自動化

テスト実行前に初期化処理を行ったり、テスト完了後にリソースの解放を行う処理をデコレーターで自動化できます。これにより、テストコードが簡潔になり、テストの保守が容易になります。

条件付きテストの実行

特定の環境や条件に基づいて、テストの実行を制御するデコレーターを使うことで、柔軟なテスト実行管理が可能です。

テストフレームワークへのデコレーター適用例

以下の例では、メソッドデコレーターを使用して、テストの前後処理やテストの条件付き実行を実装しています。

function BeforeEach(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`テストの初期化を実行します`);
    // テスト前の初期化処理
    return originalMethod.apply(this, args);
  };

  return descriptor;
}

function AfterEach(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    const result = originalMethod.apply(this, args);
    console.log(`テストの後処理を実行します`);
    // テスト後のリソース解放処理
    return result;
  };

  return descriptor;
}

function OnlyIf(condition: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
      if (condition) {
        return originalMethod.apply(this, args);
      } else {
        console.log(`テスト ${propertyKey} はスキップされました`);
      }
    };

    return descriptor;
  };
}

class TestSuite {
  @BeforeEach
  @AfterEach
  test1() {
    console.log('テスト1を実行しています');
  }

  @BeforeEach
  @AfterEach
  @OnlyIf(true)
  test2() {
    console.log('テスト2を実行しています');
  }

  @BeforeEach
  @AfterEach
  @OnlyIf(false)
  test3() {
    console.log('テスト3を実行しています');
  }
}

const testSuite = new TestSuite();
testSuite.test1();  // テストの初期化→テスト1→テスト後の処理
testSuite.test2();  // テストの初期化→テスト2→テスト後の処理
testSuite.test3();  // テスト3はスキップされる

実装例の解説

この例では、以下のようなデコレーターを使って、テストの初期化や後処理を自動化し、条件付きでテストの実行を制御しています。

  • @BeforeEach: 各テストメソッドの前に初期化処理を実行します。これにより、テストごとに必要な準備を自動的に行います。
  • @AfterEach: 各テストメソッドの後にリソース解放などの後処理を実行します。これにより、テスト後のクリーンアップを確実に行うことができます。
  • @OnlyIf: 条件がtrueの場合にのみ、テストメソッドが実行されます。これにより、特定の条件下でのみテストを行うことが可能です。test3は条件がfalseなのでスキップされます。

デコレーターによるテストフレームワークの利点

コードの簡素化

前後処理や条件付きのロジックをデコレーターで抽象化することで、各テストメソッドのコードが大幅に簡潔になります。

再利用性の向上

共通の処理や条件をデコレーターとして定義することで、テストフレームワーク全体で再利用が可能になり、コードの重複を避けられます。

柔軟なテスト管理

デコレーターを使ってテストの実行条件を管理することで、環境や要件に応じた柔軟なテスト管理が容易になります。例えば、特定のモジュールや機能のみをテストしたい場合、@OnlyIfデコレーターを活用することで効率的なテストが可能になります。

このように、デコレーターはテストフレームワークにおいても非常に強力なツールとなり、テストの自動化と効率化を実現できます。

演習問題:デコレーターを使った制御の実装

デコレーターの概念とその応用例を理解したところで、実際にデコレーターを用いたコード制御を実装してみましょう。以下の演習では、クラスデコレーターとメソッドデコレーターを組み合わせ、さまざまな条件に基づいた動的な制御を行う機能を実装します。

演習問題1: アクセス制御デコレーターの作成

あなたのタスクは、ユーザーの権限に基づいて特定のメソッドへのアクセスを制限するデコレーターを作成することです。@AdminOnlyデコレーターを使って、ユーザーが管理者でなければメソッドが実行できないように実装してください。

ヒント:

  • ユーザーの役割を示すプロパティをクラスに追加します。
  • メソッド実行前にユーザーの役割をチェックし、アクセスを制御します。
function AdminOnly(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    const user = this.user;  // ユーザー情報をクラスから取得
    if (user.role !== 'admin') {
      console.log(`エラー: ${propertyKey} は管理者のみがアクセス可能です。`);
      return;
    }
    return originalMethod.apply(this, args);
  };

  return descriptor;
}

class User {
  constructor(public name: string, public role: string) {}
}

class SecureService {
  user: User;

  constructor(user: User) {
    this.user = user;
  }

  @AdminOnly
  deleteData() {
    console.log('データが削除されました。');
  }
}

// テスト: 一般ユーザーがデータ削除を試みる
const regularUser = new User('一般ユーザー', 'user');
const service = new SecureService(regularUser);
service.deleteData();  // 管理者権限がないため、アクセスが拒否される

演習問題2: 実行回数制限デコレーターの作成

次に、メソッドが実行できる回数を制限するデコレーターを作成します。@LimitCallsデコレーターを使って、メソッドが3回までしか実行できないようにします。それ以降の実行は拒否され、警告メッセージが表示されるようにしてください。

ヒント:

  • 実行回数をクラスプロパティとして保持し、メソッド実行時にカウントします。
  • 実行回数が制限を超えると、メソッドの実行を拒否します。
function LimitCalls(limit: number) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    let callCount = 0;
    const originalMethod = descriptor.value;

    descriptor.value = function (...args: any[]) {
      if (callCount >= limit) {
        console.log(`エラー: ${propertyKey} は${limit}回までしか実行できません。`);
        return;
      }
      callCount++;
      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}

class LimitedService {
  @LimitCalls(3)
  performTask() {
    console.log('タスクを実行しています...');
  }
}

// テスト: タスクを4回実行してみる
const limitedService = new LimitedService();
limitedService.performTask();  // 実行される
limitedService.performTask();  // 実行される
limitedService.performTask();  // 実行される
limitedService.performTask();  // 実行が拒否される

演習問題3: ログデコレーターの作成

最後に、メソッドの実行前後にログを出力するデコレーターを実装してください。@LogExecutionデコレーターを作成し、メソッドの実行開始と終了時にコンソールにログを記録するようにします。

ヒント:

  • メソッドの実行前に「メソッドの開始」、実行後に「メソッドの終了」を記録します。
function LogExecution(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`メソッド ${propertyKey} の実行を開始します。`);
    const result = originalMethod.apply(this, args);
    console.log(`メソッド ${propertyKey} の実行が終了しました。`);
    return result;
  };

  return descriptor;
}

class LoggingService {
  @LogExecution
  process() {
    console.log('処理を実行中...');
  }
}

// テスト: メソッドを実行し、ログを確認する
const loggingService = new LoggingService();
loggingService.process();  // 実行前後にログが出力される

演習のまとめ

これらの演習問題では、クラスデコレーターやメソッドデコレーターを使用して、アクセス制御、実行回数の制限、ログ出力といったさまざまな機能を実装しました。デコレーターは、コードの保守性と再利用性を向上させる強力なツールです。これらの問題を通じて、デコレーターの応用力をさらに高めてください。

まとめ

本記事では、TypeScriptにおけるクラスデコレーターとメソッドデコレーターを組み合わせた高度な制御方法について解説しました。デコレーターを活用することで、コードの再利用性と保守性を大幅に向上させることができます。また、ログの自動化、権限管理、エラーハンドリング、テストフレームワークへの応用例を通じて、実際の開発におけるデコレーターの強力さを理解できたと思います。デコレーターは、複雑なロジックをシンプルにし、効率的な開発をサポートする強力なツールです。

コメント

コメントする

目次