TypeScriptクラスデコレーターの定義と活用方法を徹底解説

TypeScriptのクラスデコレーターは、オブジェクト指向プログラミングをより強力かつ柔軟にするための機能です。クラスデコレーターは、既存のクラスに新しい機能を追加したり、クラスの動作を変更したりするために使用され、コードの再利用や保守性を大幅に向上させます。デコレーターは、TypeScriptの高度な機能の一つであり、Angularのようなフレームワークでも頻繁に利用されています。本記事では、TypeScriptのクラスデコレーターの基本的な定義方法から、具体的な活用例、メリットやデメリットまでを徹底解説します。クラスデコレーターの概念をしっかりと理解し、実際のプロジェクトで役立てるための知識を身につけましょう。

目次

TypeScriptデコレーターの基本概念

デコレーターとは、クラスやメソッド、プロパティなどに対して追加の機能を付与するための特別な構文です。TypeScriptでは、デコレーターは関数として実装され、対象に対して修飾を加える形で使用されます。クラスデコレーターは、そのクラス全体に影響を与えるものであり、クラスの動作を変更したり、追加の機能を付与したりすることが可能です。

デコレーターの役割

デコレーターの主な役割は以下の通りです。

  • コードの再利用:デコレーターを利用することで、同じロジックを複数のクラスに適用できます。
  • クロスカッティング機能の実装:ログ記録、エラーハンドリング、認証処理など、複数のクラスに共通する機能を一元的に管理できます。
  • クラスの動作変更:クラスのプロパティやメソッドの動作を変更し、特定の条件下で動作を変えることが可能です。

デコレーターの種類

TypeScriptでは、以下の4種類のデコレーターを使用できます。

  1. クラスデコレーター: クラス自体に対して修飾を行います。
  2. メソッドデコレーター: メソッドの動作を修飾します。
  3. プロパティデコレーター: クラスのプロパティに対して修飾を行います。
  4. パラメータデコレーター: メソッドの引数に対して修飾を加えます。

次の章では、クラスデコレーターの具体的な定義方法と構文を見ていきます。

クラスデコレーターの構文

クラスデコレーターは、クラスの定義に対して追加の機能を与えるために使用される関数です。TypeScriptでは、クラスデコレーターは関数として定義され、対象のクラスを引数として受け取ります。クラスデコレーターは、クラスの宣言直前に@を使って記述されます。

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

以下は、クラスデコレーターの基本的な構文です。

function MyDecorator(constructor: Function) {
    console.log("デコレーターが呼び出されました");
    console.log(constructor);
}

@MyDecorator
class MyClass {
    constructor() {
        console.log("クラスのインスタンスが作成されました");
    }
}

const instance = new MyClass();

この例では、MyDecoratorというデコレーターが定義され、MyClassクラスに適用されています。デコレーター関数は、クラスのコンストラクタを引数として受け取り、そのクラスの定義を操作することが可能です。コードを実行すると、以下の出力が得られます。

デコレーターが呼び出されました
[Function: MyClass]
クラスのインスタンスが作成されました

クラスデコレーターの動作

クラスデコレーターは、クラスの宣言時に実行され、クラスそのものを変更したり、追加のメタデータを付与するために使用されます。例えば、クラスにメソッドやプロパティを追加したり、クラスのプロトタイプにアクセスして動作を変更することができます。

次の章では、実際のプロジェクトで役立つクラスデコレーターの活用例を紹介します。

クラスデコレーターの活用例

クラスデコレーターは、実際のプロジェクトでコードの再利用性や可読性を向上させるために有効に活用できます。ここでは、いくつかの具体的な活用例を紹介し、どのようにクラスデコレーターを使ってコードを効率化できるかを解説します。

ログ出力を自動化するクラスデコレーター

クラスのインスタンスが作成されたタイミングでログを出力するデコレーターを作成することで、コード内のログ処理を自動化することができます。

function LogClass(constructor: Function) {
    console.log(`${constructor.name} が作成されました。`);
}

@LogClass
class UserService {
    constructor() {
        console.log("UserServiceインスタンスを作成しました。");
    }
}

const userService = new UserService();

この例では、LogClassデコレーターがUserServiceクラスに適用されています。クラスが定義された時点で、クラス名を含むログメッセージが出力されます。実行結果は以下の通りです。

UserService が作成されました。
UserServiceインスタンスを作成しました。

認証チェックを行うクラスデコレーター

デコレーターを使用して、クラスのインスタンス化時に認証チェックを行うことも可能です。例えば、ユーザーが認証されていない場合にクラスのインスタンス化を防ぐデコレーターを作成することができます。

function RequiresAuth(constructor: Function) {
    return class extends constructor {
        constructor(...args: any[]) {
            if (!isAuthenticated()) {
                throw new Error("認証が必要です。");
            }
            super(...args);
        }
    };
}

function isAuthenticated() {
    // 簡単な認証チェック(実際の認証ロジックをここに記述)
    return false;
}

@RequiresAuth
class DashboardService {
    constructor() {
        console.log("DashboardServiceインスタンスを作成しました。");
    }
}

try {
    const dashboardService = new DashboardService();
} catch (error) {
    console.error(error.message);
}

この例では、RequiresAuthデコレーターが適用されたクラスDashboardServiceが、ユーザーの認証チェックを行い、認証されていない場合にはエラーメッセージを出力します。結果は次の通りです。

認証が必要です。

クラスのプロパティを初期化するデコレーター

クラスデコレーターを使用して、クラスのプロパティに初期値を設定することも可能です。これにより、コード全体で一貫した初期設定が保証されます。

function InitializeProperties(constructor: Function) {
    return class extends constructor {
        name = "初期値";
        age = 30;
    };
}

@InitializeProperties
class Person {
    name: string;
    age: number;

    constructor() {
        console.log(`名前: ${this.name}, 年齢: ${this.age}`);
    }
}

const person = new Person();

この例では、InitializePropertiesデコレーターが適用され、Personクラスのnameageプロパティが自動的に初期化されます。結果は次の通りです。

名前: 初期値, 年齢: 30

次の章では、クラスデコレーターを使う際のメリットとデメリットについて詳しく見ていきます。

デコレーターのメリットとデメリット

クラスデコレーターは、コードの効率化や保守性向上に役立つ強力な機能ですが、適切に使用しなければ問題を引き起こす可能性もあります。ここでは、クラスデコレーターを使用する上でのメリットとデメリットを整理し、適切な利用を支援します。

メリット

1. コードの再利用性向上

デコレーターを利用することで、複数のクラスに共通する機能を一箇所にまとめて管理できるため、同じコードを何度も書く必要がなくなります。たとえば、認証チェックやログ記録といった共通の処理をデコレーターとして作成することで、簡単に他のクラスに適用することができます。

2. クロスカッティング機能の実装が容易

デコレーターは、クラス横断的な機能(クロスカッティング機能)を簡潔に実装するのに適しています。例えば、ロギングやバリデーション、エラーハンドリングといった機能は、複数のクラスで共通する処理ですが、デコレーターを使用することで、コードの重複を減らし、保守性を向上させることができます。

3. クリーンで読みやすいコード

デコレーターを使うことで、クラスのロジックから関係のないクロスカッティングな処理を取り除くことができ、コードがシンプルで読みやすくなります。例えば、クラス内で直接認証チェックやエラーハンドリングを行うよりも、それらの処理をデコレーターにまとめることで、クラス自体のロジックが明確になります。

デメリット

1. デバッグが難しくなる場合がある

デコレーターはクラスやメソッドに対して間接的に影響を与えるため、エラーが発生した場合に、その原因を特定するのが難しくなることがあります。特に、複数のデコレーターを適用している場合、デコレーター間の相互作用によるバグが発生する可能性があります。

2. 学習コストが高い

デコレーターはTypeScriptの高度な機能であり、初心者にとっては理解が難しいことがあります。特に、複雑なデコレーターの使用や、デコレーター同士の組み合わせに関しては、十分な知識と経験が必要です。

3. 過度な抽象化による可読性の低下

デコレーターを多用しすぎると、コードの抽象度が高くなりすぎて、他の開発者が理解するのが難しくなる可能性があります。特に、プロジェクトの規模が大きくなると、デコレーターが適用されている部分を追跡するのが困難になり、結果としてコードの可読性が低下します。

次の章では、複数のデコレーターをどのようにクラスに適用できるかについて詳しく解説します。

複数デコレーターの適用方法

TypeScriptでは、1つのクラスに対して複数のデコレーターを適用することが可能です。複数のデコレーターを適用することで、クラスの挙動をさらにカスタマイズし、より柔軟な設計を行うことができます。ただし、複数デコレーターを組み合わせる際には、その適用順序や相互作用に注意が必要です。

複数デコレーターの適用構文

複数のデコレーターをクラスに適用する場合、各デコレーターは@記号を使用して、クラス宣言の上に順番に記述します。以下にその構文例を示します。

function FirstDecorator(constructor: Function) {
    console.log("FirstDecoratorが適用されました");
}

function SecondDecorator(constructor: Function) {
    console.log("SecondDecoratorが適用されました");
}

@FirstDecorator
@SecondDecorator
class MyClass {
    constructor() {
        console.log("MyClassのインスタンスが作成されました");
    }
}

const instance = new MyClass();

この場合、デコレーターは下から上へ適用されます。すなわち、SecondDecoratorが先に呼び出され、次にFirstDecoratorが呼び出されます。出力は以下の通りです。

SecondDecoratorが適用されました
FirstDecoratorが適用されました
MyClassのインスタンスが作成されました

デコレーターの適用順序の重要性

複数のデコレーターを適用する際、適用される順序は非常に重要です。例えば、あるデコレーターがクラスに新たなメソッドを追加し、そのメソッドを他のデコレーターが参照する場合、適用順序によっては期待通りに動作しないことがあります。以下はその具体例です。

function AddMethod(constructor: Function) {
    constructor.prototype.newMethod = function() {
        console.log("新しいメソッドが追加されました");
    };
}

function UseMethod(constructor: Function) {
    if (constructor.prototype.newMethod) {
        constructor.prototype.newMethod();
    } else {
        console.log("メソッドが見つかりません");
    }
}

@UseMethod
@AddMethod
class ExampleClass {
    constructor() {}
}

この例では、UseMethodデコレーターがnewMethodを呼び出そうとしていますが、適用順序の関係で、UseMethodAddMethodよりも先に呼び出されてしまうため、newMethodはまだ存在していません。その結果、メソッドが見つかりませんというメッセージが出力されます。

メソッドが見つかりません

この問題を回避するためには、デコレーターの適用順序を逆にする必要があります。

@AddMethod
@UseMethod
class ExampleClass {
    constructor() {}
}

この修正により、期待通りnewMethodが呼び出されます。

新しいメソッドが追加されました

デコレーターの依存関係管理

複数のデコレーターを組み合わせて使用する場合、それぞれのデコレーターがどのように相互作用するか、そして依存関係がないかをよく理解しておく必要があります。適切な順序や相互作用を考慮することで、クラス全体の挙動を予測可能に保つことができます。

次の章では、クラスデコレーターとプロパティデコレーターの違いを詳細に比較し、それぞれの用途について説明します。

クラスデコレーターとプロパティデコレーターの違い

TypeScriptには、クラスデコレーターだけでなく、プロパティやメソッド、パラメータに適用できるデコレーターも存在します。ここでは、特にクラスデコレーターとプロパティデコレーターの違いに焦点を当て、それぞれの用途と使い方の違いを明確にします。

クラスデコレーターとは

クラスデコレーターは、クラス全体に適用され、そのクラスの定義や振る舞いを修飾・変更するために使用されます。クラスのコンストラクタ関数を修飾し、クラス自体に変更を加えたり、追加のロジックを実行することができます。

function ClassDecorator(constructor: Function) {
    console.log("クラスデコレーターが適用されました");
    constructor.prototype.decorated = true;  // プロトタイプにプロパティを追加
}

@ClassDecorator
class MyClass {
    constructor() {
        console.log("MyClassのインスタンスが作成されました");
    }
}

const instance = new MyClass();
console.log(instance.decorated);  // true

この例では、クラスデコレーターがクラス全体に適用され、MyClassのプロトタイプにdecoratedという新しいプロパティが追加されています。クラス全体に影響を与える点が特徴です。

プロパティデコレーターとは

一方、プロパティデコレーターは、クラスの個々のプロパティに対して適用され、そのプロパティの動作を修飾します。プロパティデコレーターは、プロパティの定義時に適用され、そのプロパティのメタデータを管理するために使用されます。

function PropertyDecorator(target: any, propertyKey: string) {
    console.log(`${propertyKey} プロパティがデコレートされました`);
}

class MyClassWithProperties {
    @PropertyDecorator
    myProperty: string;

    constructor() {
        this.myProperty = "プロパティ値";
    }
}

const instance = new MyClassWithProperties();

この例では、myPropertyというクラスプロパティにプロパティデコレーターが適用されています。プロパティデコレーターは、クラス全体ではなく、特定のプロパティに対して影響を与えるのが特徴です。

クラスデコレーターとプロパティデコレーターの比較

特徴クラスデコレータープロパティデコレーター
適用対象クラス全体クラスの特定のプロパティ
主な用途クラスの定義や振る舞いを変更・修飾するプロパティのメタデータや振る舞いを修飾する
使用例ログ記録、認証チェック、クラスメタデータの追加プロパティの初期化、バリデーション、プロパティのアクセス制御

用途の違い

  • クラスデコレーターは、クラス全体に影響を与えるため、ログ記録や認証など、クラス全体で一貫して適用される機能に向いています。また、クラス自体の拡張や変更にも使用されます。
  • プロパティデコレーターは、特定のプロパティに対して振る舞いを追加したい場合に使われます。例えば、バリデーションルールをプロパティごとに設定したり、プロパティにアクセスする際の動作を制御したい場合に有効です。

次の章では、クラスデコレーターを使用して、クラスのメタデータをどのように管理できるかについて説明します。

クラスデコレーターとメタデータ管理

クラスデコレーターを使うと、クラスに対してメタデータを追加し、クラスやそのインスタンスに関する情報を管理することができます。これにより、動的にクラスの振る舞いを変更したり、クラスの特定の状態や設定を保持することが可能です。TypeScriptでは、メタデータの管理にreflect-metadataライブラリを使用することが一般的です。

メタデータとは何か

メタデータとは、オブジェクトやクラスに関する追加情報のことです。メタデータを利用することで、コード実行時にクラスやプロパティに関する情報を動的に取得したり、それに基づいて動作を変えることができます。例えば、APIリクエストに必要な情報やデータベースとのマッピング情報をクラスに持たせる場合などに役立ちます。

メタデータの追加と管理方法

reflect-metadataライブラリを使用して、クラスデコレーターにメタデータを追加し、それを管理する方法を紹介します。まずは、必要なパッケージをインストールします。

npm install reflect-metadata

次に、reflect-metadataをインポートし、メタデータを操作するコードを見ていきます。

import "reflect-metadata";

function MetaDataDecorator(target: Function) {
    Reflect.defineMetadata("role", "admin", target);
}

@MetaDataDecorator
class User {
    constructor() {}
}

console.log(Reflect.getMetadata("role", User));  // "admin"

この例では、MetaDataDecoratorを使ってUserクラスにメタデータ"role"を追加し、"admin"という値を割り当てています。Reflect.getMetadataメソッドを使うことで、クラスからそのメタデータを取得することができます。

プロパティへのメタデータの適用

クラス全体だけでなく、プロパティに対してもメタデータを設定できます。プロパティデコレーターと組み合わせることで、プロパティに追加の情報を持たせ、後から動的に操作することが可能です。

function PropertyMetaData(key: string, value: any) {
    return function (target: any, propertyKey: string) {
        Reflect.defineMetadata(key, value, target, propertyKey);
    };
}

class User {
    @PropertyMetaData("role", "user")
    name: string;

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

const user = new User("John");
console.log(Reflect.getMetadata("role", user, "name"));  // "user"

この例では、Userクラスのnameプロパティに対して、メタデータ"role""user"という値が割り当てられています。Reflect.getMetadataメソッドでそのメタデータを取得できます。

メタデータを活用した柔軟な設計

メタデータを使用すると、クラスやプロパティに追加情報を持たせることができるため、以下のような柔軟な設計が可能です。

  • 依存性注入:メタデータを使ってクラス間の依存関係を管理し、動的にインスタンスを生成します。
  • バリデーション:各プロパティにメタデータとしてバリデーションルールを追加し、入力値のチェックを自動化します。
  • 動的なルーティング:クラスやメソッドに対してルート情報をメタデータとして定義し、実行時に動的にルートを生成します。

次の章では、TypeScriptデコレーターを使う際の注意点とベストプラクティスを紹介します。

TypeScriptデコレーターの注意点

TypeScriptのデコレーターは強力な機能ですが、使用にはいくつかの注意点とベストプラクティスがあります。適切にデコレーターを使用することで、コードの保守性や可読性を向上させることができますが、誤った使い方をすると、バグやデバッグの困難さに繋がる可能性があります。ここでは、デコレーターを使う際の注意点とベストプラクティスを解説します。

1. デコレーターは実験的機能

TypeScriptのデコレーターは現在も実験的な機能であり、将来的に仕様が変更される可能性があります。そのため、使用する前に、プロジェクトでデコレーターが本当に必要かどうかを慎重に検討する必要があります。また、デコレーターを使用する場合は、TypeScriptのバージョンや環境の互換性に留意してください。

2. デコレーターの副作用に注意

デコレーターは、クラスやプロパティの動作を変更するため、思いもよらない副作用を引き起こすことがあります。特に複数のデコレーターを組み合わせて使用する場合、各デコレーターの影響範囲を明確に理解しておく必要があります。適用する順序や、デコレーター同士が互いに影響を及ぼす可能性についても十分に確認してください。

例: デコレーターの順序による副作用

デコレーターは、適用される順番が重要です。以下の例では、デコレーターの順序によって動作が変わります。

function FirstDecorator(constructor: Function) {
    console.log("FirstDecoratorが呼び出されました");
}

function SecondDecorator(constructor: Function) {
    console.log("SecondDecoratorが呼び出されました");
}

@FirstDecorator
@SecondDecorator
class ExampleClass {}

この例では、SecondDecoratorが先に呼び出され、その後FirstDecoratorが呼び出されます。順序が逆になると、実行結果も異なります。

3. デバッグが難しくなる可能性

デコレーターを使うと、コードの一部が間接的に変更されるため、デバッグが難しくなることがあります。デコレーターがどのタイミングで呼び出され、どのようにクラスやプロパティを変更しているのかを把握することが難しくなり、バグの原因追及が困難になる場合があります。そのため、デコレーターの挙動や動作をログで確認するなどの工夫が必要です。

4. 過度な抽象化を避ける

デコレーターを使いすぎると、コードが過度に抽象化され、可読性が低下することがあります。特に他の開発者がコードを理解するのが難しくなる可能性があるため、デコレーターを使用する際には、適切なコメントやドキュメントを残すことが重要です。デコレーターを使う場面は、慎重に選び、必要な場合に限ることがベストです。

5. テストの複雑化

デコレーターはクラスやプロパティに動的な変更を加えるため、テストコードを書く際に注意が必要です。デコレーターがクラスやメソッドにどのような影響を与えているのかを十分に理解し、テストでその影響をカバーできるように設計する必要があります。特に、複数のデコレーターを組み合わせて使用する場合、すべてのケースを網羅したテストを行うことが重要です。

ベストプラクティス

1. 単一責任の原則を守る

デコレーターは一つの機能に対して集中して処理を行うように設計すべきです。デコレーターが複数の機能を担当すると、コードが複雑化し、保守が難しくなります。できるだけ、デコレーターごとに一つの責任を持たせることが理想的です。

2. コメントやドキュメントを充実させる

デコレーターを使用する場合、その処理がどのように動作しているのかが他の開発者にとって分かりやすいように、適切なコメントやドキュメントを残すことが推奨されます。特に、複数のデコレーターを組み合わせる場合は、その適用順序や動作の説明を含めると良いでしょう。

3. ログを活用する

デコレーターの処理が正しく動作しているか確認するために、ログを活用することが有効です。特に、クラスデコレーターやメソッドデコレーターでは、クラスがインスタンス化されたタイミングやメソッドが呼び出されたタイミングでログを出力すると、デバッグや動作確認がしやすくなります。

次の章では、独自のクラスデコレーターを作成する方法を実践的に解説します。

実践:カスタムデコレーターの作成

TypeScriptでは、独自のカスタムデコレーターを作成して、クラスやプロパティに対して特定の機能を追加することが可能です。この章では、カスタムデコレーターの作成方法をステップバイステップで解説し、具体的な実装例を通してその活用方法を紹介します。

カスタムクラスデコレーターの作成方法

カスタムクラスデコレーターは、クラス自体に適用され、特定の機能を追加したり、既存の動作を修正するために使用されます。基本的なクラスデコレーターの構造は、以下の通りです。

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

このデコレーターは、適用されたクラスの名前をログ出力するだけのシンプルなものです。このデコレーターをクラスに適用するには、クラスの宣言前に@記号を使って適用します。

@MyDecorator
class MyClass {
    constructor() {
        console.log("MyClassのインスタンスが作成されました");
    }
}

const instance = new MyClass();

実行結果は以下の通りです:

MyClass クラスがデコレートされました
MyClassのインスタンスが作成されました

メタデータ付きのカスタムデコレーター

さらに、デコレーターを使ってメタデータを追加することも可能です。以下は、クラスにロール(役割)情報を追加するデコレーターの例です。

function Role(role: string) {
    return function (constructor: Function) {
        Reflect.defineMetadata("role", role, constructor);
    };
}

@Role("admin")
class AdminUser {
    constructor() {}
}

console.log(Reflect.getMetadata("role", AdminUser));  // "admin"

この例では、@Role("admin")というデコレーターを使用して、AdminUserクラスにroleメタデータを追加しています。Reflect.getMetadataを使用することで、クラスに追加されたメタデータを取得できます。

動的な振る舞いを追加するデコレーター

カスタムデコレーターは、クラスの振る舞いを動的に変更することも可能です。以下は、クラスがインスタンス化された際にプロパティの値を動的に変更するデコレーターの例です。

function ModifyInstance(constructor: Function) {
    return class extends constructor {
        newProperty = "新しいプロパティ";
        existingProperty = "変更されたプロパティ";
    };
}

@ModifyInstance
class ExampleClass {
    existingProperty = "元のプロパティ";

    constructor() {
        console.log(this.existingProperty);  // 変更後のプロパティが表示される
    }
}

const instance = new ExampleClass();
console.log(instance.newProperty);  // "新しいプロパティ"

この例では、ModifyInstanceデコレーターがExampleClassのインスタンスに対して新しいプロパティを追加し、既存のプロパティの値を変更しています。これにより、インスタンスが作成されると、新しいプロパティや修正されたプロパティが反映されます。

実践例:ログを出力するカスタムデコレーター

次に、実際のアプリケーションでよく使用される例として、クラスのメソッドが呼び出された際に自動的にログを出力するカスタムデコレーターを作成します。

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

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

    return descriptor;
}

class Calculator {
    @LogMethod
    add(a: number, b: number): number {
        return a + b;
    }
}

const calculator = new Calculator();
calculator.add(5, 10);  // ログが出力され、計算結果が返される

この例では、LogMethodデコレーターを使用して、Calculatorクラスのaddメソッドが呼び出される際にログを出力するようにしています。実行結果は次の通りです。

メソッド add が呼び出されました。引数: 5,10

このように、デコレーターを活用することで、コードの再利用やメンテナンス性を向上させることができます。

次の章では、デコレーターの実践力を高めるための演習問題を紹介します。

演習問題: デコレーターを使ったプロジェクト

ここでは、TypeScriptのデコレーターを実践的に使用する力を高めるための演習問題をいくつか紹介します。これらの課題を通じて、デコレーターの作成や応用方法をより深く理解できるでしょう。

演習1: バリデーションデコレーターの作成

クラスのプロパティに対してバリデーションルールを追加するプロパティデコレーターを作成してください。このデコレーターは、値が指定された範囲内に収まっているかをチェックするものとします。もし範囲外の値が入力された場合、エラーをスローする機能を持たせてください。

要件:

  • 数値プロパティに適用し、指定された範囲内の値であることを検証する
  • 範囲外の場合はエラーメッセージを表示する

サンプルコード:

function ValidateRange(min: number, max: number) {
    return function (target: any, propertyKey: string) {
        let value: number;

        const getter = function() {
            return value;
        };

        const setter = function(newVal: number) {
            if (newVal < min || newVal > max) {
                throw new Error(`${propertyKey}は${min}から${max}の範囲でなければなりません`);
            }
            value = newVal;
        };

        Object.defineProperty(target, propertyKey, {
            get: getter,
            set: setter,
            enumerable: true,
            configurable: true
        });
    };
}

class Product {
    @ValidateRange(1, 100)
    price: number;

    constructor(price: number) {
        this.price = price;
    }
}

const product = new Product(120);  // エラー: priceは1から100の範囲でなければなりません

演習2: キャッシュデコレーターの作成

メソッドの結果をキャッシュし、同じ引数で再度呼び出されたときにはキャッシュされた結果を返すメソッドデコレーターを作成してください。

要件:

  • メソッドの結果を引数ごとにキャッシュする
  • 同じ引数で呼び出された場合、キャッシュされた結果を返す

サンプルコード:

function Cache(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    const cache = new Map<string, any>();

    descriptor.value = function (...args: any[]) {
        const key = JSON.stringify(args);
        if (cache.has(key)) {
            console.log("キャッシュされた結果を返します");
            return cache.get(key);
        }
        const result = originalMethod.apply(this, args);
        cache.set(key, result);
        return result;
    };

    return descriptor;
}

class Calculator {
    @Cache
    add(a: number, b: number): number {
        console.log("計算中...");
        return a + b;
    }
}

const calculator = new Calculator();
console.log(calculator.add(5, 10));  // 計算中... 15
console.log(calculator.add(5, 10));  // キャッシュされた結果を返します 15

演習3: 権限デコレーターの作成

メソッドにアクセスできるユーザーの権限を制限するカスタムデコレーターを作成してください。このデコレーターは、指定された権限を持つユーザーのみがメソッドを実行できるように制限する機能を実装します。

要件:

  • メソッドにアクセスできるユーザー権限をチェックする
  • 権限が不足している場合はエラーメッセージを表示する

サンプルコード:

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

        descriptor.value = function (...args: any[]) {
            const userRole = getUserRole();  // 仮の関数、ユーザーの権限を取得する
            if (userRole !== roleRequired) {
                throw new Error(`メソッド ${propertyKey} は${roleRequired}の権限が必要です`);
            }
            return originalMethod.apply(this, args);
        };

        return descriptor;
    };
}

// 仮の関数:ユーザーの権限を取得する
function getUserRole() {
    return "user";  // 現在のユーザーの権限を返す
}

class AdminService {
    @Authorize("admin")
    deleteUser() {
        console.log("ユーザーを削除しました");
    }
}

const adminService = new AdminService();
adminService.deleteUser();  // エラー: メソッド deleteUser はadminの権限が必要です

まとめ

これらの演習問題を通じて、デコレーターの実践的な使い方を学び、クラスやプロパティの動作をカスタマイズする力を養います。次の章では、これまでの内容を振り返り、まとめを行います。

まとめ

本記事では、TypeScriptのクラスデコレーターの定義と活用方法について詳しく解説しました。デコレーターの基本概念から、その構文、活用例、注意点、さらにカスタムデコレーターの作成まで、実践的な知識を身につけることができたと思います。クラスデコレーターを使うことで、コードの再利用性や保守性を高めることができる一方、デバッグや使用の際には注意が必要です。演習問題を通して、より深く理解を深め、実際のプロジェクトで応用していきましょう。

コメント

コメントする

目次