TypeScriptでの静的プロパティと静的メソッドを使用したシングルトンパターンの実装方法

TypeScriptでシングルトンパターンを実装する方法は、ソフトウェア開発において非常に有効です。シングルトンパターンは、クラスのインスタンスを1つだけに制限し、そのインスタンスをどこからでもアクセスできるようにするデザインパターンです。これにより、設定管理やログ出力など、グローバルに共有されるべきリソースを効率的に管理することが可能です。

本記事では、TypeScriptの静的プロパティと静的メソッドを用いて、シングルトンパターンを実装する方法について詳しく解説します。また、実装の際に役立つコード例や、利点・注意点についても取り上げ、理解を深めるための応用例や演習問題も紹介します。これにより、TypeScriptを使った効果的なシングルトンパターンの構築方法が理解できるようになります。

目次

シングルトンパターンとは

シングルトンパターンは、オブジェクト指向デザインパターンの1つで、特定のクラスに対してインスタンスが1つしか存在しないことを保証します。このパターンでは、同じクラスの複数のインスタンスが生成されないように制御され、どこからでも同じインスタンスにアクセスできるという特徴があります。

シングルトンパターンの用途

シングルトンパターンは、アプリケーション全体で共有されるリソースを管理する際に便利です。例えば、次のようなケースでよく利用されます:

1. 設定管理

アプリケーションの設定や構成情報を管理する場合、シングルトンとして1つのインスタンスを生成し、全てのコンポーネントがそのインスタンスを参照することで、設定の一貫性が保たれます。

2. ログ出力

ログの出力管理でもシングルトンが有効です。全てのクラスやモジュールから共通のログ機能にアクセスできるようにすることで、複数のログ出力オブジェクトを作成する必要がなくなります。

シングルトンパターンは、インスタンス管理の簡素化やメモリ効率の向上、リソースの一貫性を保つために重要な役割を果たします。

TypeScriptにおける静的プロパティの役割

静的プロパティは、TypeScriptのクラスにおいて、クラスのインスタンスに依存せずにクラス自体に紐づけられるプロパティです。通常のプロパティは各インスタンスごとに異なる値を持ちますが、静的プロパティはクラスごとに一つだけ存在し、全てのインスタンスで共有されます。

静的プロパティの使用例

シングルトンパターンを実装する際、静的プロパティを使用して唯一のインスタンスを管理するのが一般的です。例えば、次のようなコードで静的プロパティを利用します:

class Singleton {
  private static instance: Singleton;

  private constructor() {}

  public static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
}

この例では、instanceという静的プロパティがクラス内で宣言されています。このプロパティはクラスそのものに関連付けられており、インスタンスを生成する際に使われることで、クラスが1つだけのインスタンスを持つことを保証します。

静的プロパティの利点

静的プロパティの最大の利点は、クラスのインスタンスに依存せずに情報を共有できる点です。シングルトンパターンでは、静的プロパティを使って唯一のインスタンスを保持することで、どこからでもアクセス可能なインスタンス管理が容易になります。また、メモリの効率化や、グローバルな状態管理を行う際にも有効です。

静的メソッドの使い方

静的メソッドは、静的プロパティと同様に、クラス自体に紐づけられたメソッドであり、インスタンス化せずに呼び出すことができます。シングルトンパターンでは、静的メソッドを用いてインスタンスの生成と取得を管理します。

静的メソッドによるインスタンス管理

シングルトンパターンの実装において、静的メソッドを活用して唯一のインスタンスを生成する方法が一般的です。以下のコード例では、getInstanceという静的メソッドを使用して、シングルトンインスタンスの生成と取得を行っています。

class Singleton {
  private static instance: Singleton;

  // コンストラクタは外部から呼び出せないようにprivateに設定
  private constructor() {}

  // 静的メソッドを使ってインスタンスを管理
  public static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
}

このコードで注目すべき点は、getInstanceメソッドです。このメソッドはインスタンスを作成する責任を持ち、すでにインスタンスが存在する場合は、それを返す役割を果たします。初めて呼び出されたときだけ、新しいインスタンスが生成され、それ以降は同じインスタンスが返されます。

静的メソッドの利点

静的メソッドを使うことの利点は、クラスの外部から容易にアクセスできることです。シングルトンパターンにおいては、getInstanceのような静的メソッドを使うことで、クラスをインスタンス化せずに特定の機能にアクセス可能になり、コードの簡素化と一貫性を保つことができます。

また、静的メソッドを使うことで、シングルトンパターンにおけるインスタンスの生成管理をクラスレベルで行い、明示的に制御することができるため、設計の柔軟性と効率を向上させます。

TypeScriptでのシングルトンの実装例

ここでは、TypeScriptを使用してシングルトンパターンを実装する具体的なコード例を紹介します。この実装では、静的プロパティと静的メソッドを組み合わせて、1つのインスタンスしか存在しないことを保証します。

シングルトンパターンの実装コード

以下がTypeScriptでシングルトンパターンを実装したサンプルコードです。

class Singleton {
  // クラスの静的プロパティでインスタンスを保持
  private static instance: Singleton;

  // コンストラクタはprivateに設定し、外部からのインスタンス化を防ぐ
  private constructor() {
    console.log("シングルトンインスタンスが作成されました");
  }

  // 静的メソッドでインスタンスの取得を行う
  public static getInstance(): Singleton {
    // インスタンスが存在しない場合は新しく作成する
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    // すでに存在する場合はそのインスタンスを返す
    return Singleton.instance;
  }

  // クラス内で利用できるメソッド
  public showMessage(): void {
    console.log("このメッセージはシングルトンインスタンスから呼び出されています。");
  }
}

コード解説

  1. 静的プロパティ instance
    このプロパティは、シングルトンのインスタンスを保持するために使用されます。privateとして宣言されているため、外部から直接アクセスすることはできません。
  2. コンストラクタの private指定
    private constructor() により、クラスの外部からインスタンスを新たに作成することができなくなります。これによって、開発者が誤って新しいインスタンスを生成するのを防ぎます。
  3. getInstance() 静的メソッド
    このメソッドがシングルトンパターンの核心部分です。instanceが未定義の場合、新しいインスタンスを作成し、定義済みなら既存のインスタンスを返します。このようにして、常に同じインスタンスが返されることが保証されます。
  4. メソッド showMessage()
    このメソッドは、シングルトンインスタンスから呼び出されるメソッドの一例です。どのクラスやモジュールからでもシングルトンのメソッドを使用できることを示しています。

使用例

このシングルトンを利用する際は、以下のように getInstance メソッドを呼び出してインスタンスを取得し、そのインスタンス上でメソッドを実行できます。

const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();

instance1.showMessage();  // "このメッセージはシングルトンインスタンスから呼び出されています。"

// instance1 と instance2 は同じインスタンスを指している
console.log(instance1 === instance2);  // true

このコードでは、instance1instance2は同じシングルトンインスタンスを指していることが確認できます。

シングルトンパターンの利点

シングルトンパターンを用いることで、クラスのインスタンスが1つだけであることを保証するため、特定のシナリオでは大きな利点をもたらします。ここでは、シングルトンパターンの主な利点について説明します。

1. メモリの効率化

シングルトンパターンを採用することで、インスタンスを1つしか生成しないため、メモリを無駄に消費することがありません。特に、大規模なアプリケーションやリソースが限られた環境では、複数のインスタンスを作成しないことによるパフォーマンスの向上が期待できます。

2. 状態の一貫性

シングルトンインスタンスは、アプリケーション全体で同じ状態を維持するため、設定やデータの管理が一貫して行われます。例えば、設定情報をシングルトンインスタンスで管理する場合、異なるコンポーネント間で設定の不整合が起こりにくくなります。

3. グローバルアクセスの容易さ

シングルトンパターンでは、どこからでもインスタンスにアクセスできるため、グローバルに共有するリソースを簡単に利用できます。特定の機能やデータを複数のモジュールで共有する必要がある場合、シングルトンは効率的なソリューションです。

4. ログやデータベース接続の管理

シングルトンパターンは、ログ管理やデータベース接続など、1つのインスタンスだけで十分なリソースの管理に適しています。これにより、システム全体で同じインスタンスを使用して安定した処理を行えるため、エラーやリソースの競合を減らすことができます。

5. 実装の簡便さ

シングルトンパターンは比較的簡単に実装できるため、複雑なインスタンス管理を避けたい場合に適したデザインパターンです。TypeScriptでは、静的プロパティや静的メソッドを用いることで、コードもシンプルでわかりやすく保つことができます。

以上のように、シングルトンパターンはメモリ効率や一貫性、グローバルアクセスを保証し、複数のインスタンスが不要な場合に有効です。特に、状態を共有する必要があるアプリケーションでは、その利点が顕著に現れます。

実装時の注意点

シングルトンパターンは便利なデザインパターンですが、その実装にはいくつかの注意点があります。これらのポイントを無視すると、予期せぬエラーやパフォーマンスの低下を招く可能性があります。ここでは、TypeScriptでシングルトンを実装する際に気をつけるべき点を説明します。

1. 複数スレッド環境での競合

シングルトンパターンは通常、単一スレッドのアプリケーションに適しており、複数スレッド環境では注意が必要です。例えば、サーバーサイドのTypeScriptでNode.jsを使用している場合、同時に複数のスレッドからインスタンスを生成しようとすると、インスタンスが複数作られてしまうことがあります。このような競合を避けるためには、インスタンスの生成時にロック機構を導入するなど、スレッドセーフな設計が必要です。

2. 初期化のタイミング

シングルトンインスタンスが初期化されるタイミングにも注意が必要です。特に、インスタンスの初期化に時間がかかる場合や、依存関係がある場合は、必要なタイミングで確実に初期化が行われるように管理しなければなりません。遅延初期化(Lazy Initialization)を使用して、最初に必要になったときにインスタンスを生成するのが有効な手法です。

public static getInstance(): Singleton {
  if (!Singleton.instance) {
    // 遅延初期化
    Singleton.instance = new Singleton();
  }
  return Singleton.instance;
}

3. テストの困難さ

シングルトンはグローバルな状態を持つため、ユニットテストやインテグレーションテストを行う際に難点となる場合があります。テスト時に複数のインスタンスを生成できないため、状態が予測不可能になったり、他のテストケースに影響を及ぼしたりする可能性があります。この問題を避けるためには、シングルトンの状態をリセットする機能や、モックを使ったテスト手法を導入することが推奨されます。

4. 過度な依存を避ける

シングルトンパターンは便利ですが、過度に使用するとコードが単一のインスタンスに依存しすぎてしまうことがあります。これにより、コードの柔軟性が低下し、変更が難しくなる可能性があります。特に、モジュールの依存関係が強くなりすぎると、シングルトンの変更や拡張が難しくなり、システム全体に影響を与えることがあります。シングルトンを利用する際は、必要な場所だけに限定し、過度な依存を避けることが重要です。

5. メモリリークのリスク

シングルトンパターンでは、アプリケーションが終了するまでインスタンスが保持されるため、メモリリークの原因になることがあります。特に、シングルトンインスタンスが大量のリソースを保持している場合、適切にメモリが解放されないことで、アプリケーション全体のメモリ使用量が増加するリスクがあります。不要になったデータやリソースは適切に管理し、メモリ消費を最小限に抑える工夫が必要です。

シングルトンパターンは強力なツールですが、これらの注意点を意識して実装することで、予期しない問題を防ぎ、効率的で安全なコードを保つことができます。

応用例:設定管理におけるシングルトン

シングルトンパターンの一般的な応用例の1つに、設定管理があります。多くのアプリケーションでは、設定や環境変数をアプリケーション全体で共有する必要がありますが、これを効果的に管理するためにシングルトンが使用されます。ここでは、設定管理をシングルトンで実装する例を紹介します。

設定管理の必要性

アプリケーションの設定情報には、データベースの接続情報、APIキー、ファイルパスなど、さまざまなものが含まれます。これらの情報を複数の箇所で管理すると、一貫性を保つのが難しくなります。シングルトンパターンを使用することで、これらの設定を一元管理し、どこからでもアクセスできるようにすることが可能になります。

シングルトンによる設定管理の実装例

以下は、TypeScriptで設定管理をシングルトンパターンを用いて実装する例です。この例では、アプリケーション全体で共有される設定を管理しています。

class ConfigurationManager {
  private static instance: ConfigurationManager;
  private config: { [key: string]: any };

  // コンストラクタをprivateにして、外部からの直接インスタンス化を防ぐ
  private constructor() {
    // 設定情報を初期化
    this.config = {
      apiKey: "12345-ABCDE",
      dbConnectionString: "postgres://user:password@localhost:5432/mydb",
      logLevel: "debug",
    };
  }

  // シングルトンインスタンスの取得メソッド
  public static getInstance(): ConfigurationManager {
    if (!ConfigurationManager.instance) {
      ConfigurationManager.instance = new ConfigurationManager();
    }
    return ConfigurationManager.instance;
  }

  // 設定を取得するメソッド
  public get(key: string): any {
    return this.config[key];
  }

  // 設定を更新するメソッド
  public set(key: string, value: any): void {
    this.config[key] = value;
  }
}

このコードでは、ConfigurationManagerクラスがシングルトンとして設定情報を管理しています。設定は、getInstanceメソッドを通してインスタンスを取得し、getメソッドやsetメソッドを使ってアクセス・更新します。

設定管理の使用例

シングルトンによる設定管理を活用する際のコード例です。

// 設定管理のシングルトンインスタンスを取得
const configManager = ConfigurationManager.getInstance();

// 設定情報を取得
const apiKey = configManager.get("apiKey");
console.log(`APIキー: ${apiKey}`);

// 設定情報を更新
configManager.set("logLevel", "info");
console.log(`新しいログレベル: ${configManager.get("logLevel")}`);

この例では、アプリケーション全体で共有される設定情報を簡単に管理できることがわかります。ConfigurationManagerのインスタンスは1つだけなので、どの部分からアクセスしても同じ設定情報を取得または変更することができます。

応用場面

設定管理の他にも、シングルトンパターンは以下のようなシナリオで役立ちます。

  • データベース接続管理: 1つのデータベース接続インスタンスを全アプリケーションで共有。
  • ログ管理: 1つのロガーを使ってアプリケーション全体のログを一貫して管理。
  • キャッシュ管理: システム全体で1つのキャッシュインスタンスを利用し、データの再取得を防止。

このように、シングルトンパターンは設定管理を含む多くのシナリオで活用でき、アプリケーションの一貫性を保つために非常に効果的です。

テスト方法

シングルトンパターンのテストは他のデザインパターンに比べてやや難しい点があります。シングルトンは常に同じインスタンスを返すため、テスト時に状態が共有されてしまい、テストケース同士が影響し合うことがあります。ここでは、シングルトンパターンのテストを行う際の方法と注意点について解説します。

シングルトンパターンのテストの課題

シングルトンパターンでは、インスタンスが1つしか存在しないため、次のような問題が生じることがあります。

  • 状態がテスト間で共有される:シングルトンインスタンスの状態が1つだけなので、あるテストで状態を変更すると、他のテストケースにも影響を与えてしまいます。
  • モックが使いにくい:シングルトンは1つのインスタンスしか存在しないため、モックを使ったテストがやりづらくなります。依存関係の注入が難しい場合があります。

これらの問題に対処するためのテスト戦略を以下で紹介します。

状態リセットの実装

シングルトンのテストを行う際、各テストケースで状態がリセットされるようにするのが良い方法です。TypeScriptでは、テスト用のメソッドやフレームワークを使って、テスト前後でシングルトンインスタンスの状態をリセットすることができます。例えば、次のようなコードで状態をリセットする方法を取り入れます。

class Singleton {
  private static instance: Singleton;
  private data: string;

  private constructor() {
    this.data = "初期データ";
  }

  public static getInstance(): Singleton {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }

  public getData(): string {
    return this.data;
  }

  public setData(data: string): void {
    this.data = data;
  }

  // テスト用のリセットメソッド
  public static resetInstance(): void {
    Singleton.instance = null;
  }
}

ここでは、resetInstanceメソッドを使ってシングルトンインスタンスをリセットできるようにしています。これを使うことで、テストごとに新しいインスタンスを生成することが可能になります。

テストの実装例

Jestなどのテストフレームワークを使用して、シングルトンの動作をテストする方法の例を紹介します。

describe("Singleton Pattern Tests", () => {
  beforeEach(() => {
    Singleton.resetInstance(); // テストごとにインスタンスをリセット
  });

  it("should return the same instance", () => {
    const instance1 = Singleton.getInstance();
    const instance2 = Singleton.getInstance();

    expect(instance1).toBe(instance2); // 同じインスタンスが返されるかを確認
  });

  it("should maintain the state", () => {
    const instance = Singleton.getInstance();
    instance.setData("新しいデータ");

    expect(instance.getData()).toBe("新しいデータ");
  });

  it("should reset the instance state", () => {
    const instance = Singleton.getInstance();
    instance.setData("新しいデータ");

    Singleton.resetInstance(); // インスタンスをリセット

    const newInstance = Singleton.getInstance();
    expect(newInstance.getData()).toBe("初期データ"); // インスタンスがリセットされたかを確認
  });
});

このテストケースでは、beforeEachフックを使って各テストの前にシングルトンの状態をリセットしています。これにより、各テストが独立して動作し、他のテストの影響を受けないようにしています。

モックを使ったテスト

シングルトンパターンをテストする際に、モックを使って外部の依存関係をシミュレーションすることもできます。例えば、シングルトンがデータベース接続やAPIコールを含む場合、これらの部分をモックに置き換えてテストを行うことが推奨されます。

jest.mock("./DatabaseService"); // 依存するモジュールをモック

describe("Singleton with Mock Tests", () => {
  it("should use a mock database connection", () => {
    const mockDbInstance = new DatabaseService();
    mockDbInstance.connect.mockReturnValue("Mocked connection");

    const instance = Singleton.getInstance();
    expect(instance.connectToDb()).toBe("Mocked connection");
  });
});

このように、モックを使うことで外部の依存関係に影響されないテストを実行でき、シングルトンパターンの動作を正確に検証できます。

シングルトンパターンをテストする際には、状態のリセットやモックを活用することで、他のテストに影響を与えずにテストの精度を高めることができます。

シングルトンパターンのアンチパターン

シングルトンパターンは便利で強力なデザインパターンですが、誤用するとコードのメンテナンス性や可読性に悪影響を与えます。ここでは、シングルトンパターンのアンチパターン、つまり避けるべき使い方や落とし穴について解説します。

1. グローバル変数の乱用

シングルトンはグローバルアクセスが容易であるため、必要以上に多くのデータをシングルトンに詰め込むことがあります。これは、シングルトンを「グローバル変数」として使用してしまう典型的なアンチパターンです。グローバル状態を持つと、依存関係が複雑になり、コードのテストやデバッグが困難になります。

例えば、以下のような例は典型的な誤用です。

class ConfigSingleton {
  private static instance: ConfigSingleton;
  public settings: { [key: string]: any } = {}; // グローバル変数的に使われている

  private constructor() {}

  public static getInstance(): ConfigSingleton {
    if (!ConfigSingleton.instance) {
      ConfigSingleton.instance = new ConfigSingleton();
    }
    return ConfigSingleton.instance;
  }
}

シングルトンにアプリケーション全体の状態を持たせてしまうと、複数のクラスがこのインスタンスに依存することになり、変更や修正が他の部分に影響を与えやすくなります。これを避けるためには、シングルトンは本来の役割に限定し、データの保持を最小限に抑えるべきです。

2. 依存関係の強化

シングルトンは一度作成したインスタンスをアプリケーション全体で共有するため、依存関係が強くなることがあります。特に、複数のクラスやモジュールがシングルトンインスタンスに依存しすぎると、シングルトンを変更する際に他の部分にも広範な影響を与えてしまう可能性があります。

依存性注入(Dependency Injection)を使って依存関係を明示的に管理することで、シングルトンへの依存を緩和できます。例えば、次のようにシングルトンを注入して使用することが理想的です。

class SomeService {
  private config: ConfigSingleton;

  constructor(config: ConfigSingleton) {
    this.config = config; // 明示的な依存関係注入
  }
}

このように、シングルトンを直接利用するのではなく、依存関係注入を使ってクラス間の結合を緩めることで、柔軟性が高く保守性のある設計が可能になります。

3. テストの困難さ

シングルトンは、1つのインスタンスしか存在しないため、テスト環境でモックを使うことやインスタンスをリセットするのが難しくなります。これにより、テストがしにくくなり、ユニットテストや自動化テストの妨げになります。この問題を避けるためには、リセット可能なインスタンス設計や、モックを使ったテスト戦略を導入する必要があります。

class ResettableSingleton {
  private static instance: ResettableSingleton;

  private constructor() {}

  public static getInstance(): ResettableSingleton {
    if (!ResettableSingleton.instance) {
      ResettableSingleton.instance = new ResettableSingleton();
    }
    return ResettableSingleton.instance;
  }

  // テスト用にインスタンスをリセットできるようにする
  public static resetInstance(): void {
    ResettableSingleton.instance = null;
  }
}

これにより、テスト中に状態をリセットしたり、モックを使ってシングルトンの挙動を柔軟にコントロールできるようになります。

4. 隠れた依存関係の問題

シングルトンパターンは一見すると依存関係を簡素化しているように見えますが、実際には隠れた依存関係を作り出すことがあります。シングルトンがグローバルにアクセス可能であるため、コードのどこでこの依存関係が生じているかが不明確になりやすいのです。結果として、アプリケーションの一部を修正した際に、意図しない部分に影響を及ぼす可能性が高まります。

隠れた依存関係を避けるためには、シングルトンを使う場所を明確にし、依存関係がどこに存在するのかを文書化することが重要です。また、依存関係注入や明示的なインターフェースを使用することで、依存関係を可視化しやすくなります。

5. ライフサイクル管理の問題

シングルトンパターンを使用する際、オブジェクトのライフサイクル管理が曖昧になりがちです。特に長時間稼働するサーバーアプリケーションなどでは、シングルトンインスタンスが長期間メモリに保持され続け、不要なリソースを消費することがあります。これを防ぐためには、必要に応じてインスタンスを破棄したり、メモリリークを防止するメカニズムを実装することが必要です。

public static destroyInstance(): void {
  Singleton.instance = null; // インスタンスの破棄
}

これにより、不要になったシングルトンインスタンスを適切に破棄し、リソースの管理を行うことができます。

シングルトンパターンは非常に強力ですが、適切に使用しないと多くの問題を引き起こします。これらのアンチパターンを避け、シングルトンを効果的に使うための設計と運用を心がけることが重要です。

演習問題:シングルトンパターンの応用

シングルトンパターンの実装とその利点、注意点を理解したうえで、応用できるかどうかを確認するための演習問題を出題します。これらの演習を通じて、シングルトンパターンの活用方法をより深く理解し、実際のプロジェクトで活用できるようにしましょう。

演習1: 設定ファイルの動的読み込み

シングルトンパターンを使用して、設定情報を管理するクラスを実装してください。このクラスは、設定ファイルを読み込み、そのデータをグローバルにアクセスできる形で管理します。また、設定ファイルを動的に再読み込みする機能を持たせ、変更された内容がアプリケーション全体に反映されるように実装してください。

要求事項:

  1. 設定ファイルは初回起動時に1度読み込まれます。
  2. 設定を更新するためのメソッド reloadConfig() を実装し、変更が即座に反映されるようにしてください。
  3. 設定値を取得するメソッド get(key: string): any を実装してください。
class ConfigManager {
  private static instance: ConfigManager;
  private config: { [key: string]: any };

  private constructor() {
    this.loadConfig();
  }

  // シングルトンインスタンスの取得
  public static getInstance(): ConfigManager {
    if (!ConfigManager.instance) {
      ConfigManager.instance = new ConfigManager();
    }
    return ConfigManager.instance;
  }

  // 設定ファイルの読み込み
  private loadConfig(): void {
    this.config = {
      apiEndpoint: "https://api.example.com",
      retryAttempts: 3,
    };
  }

  // 設定値を取得
  public get(key: string): any {
    return this.config[key];
  }

  // 設定ファイルを再読み込み
  public reloadConfig(): void {
    console.log("設定ファイルを再読み込み中...");
    this.loadConfig();
  }
}

課題: 上記のコードをベースに、ファイルシステムから実際の設定ファイルを読み込むように拡張してください(ファイルの読み込みは模擬的で構いません)。


演習2: ログ管理システムの設計

シングルトンパターンを使用して、システム全体で利用されるログ管理クラスを設計してください。複数のクラスやモジュールから同じロガーインスタンスにアクセスできるようにします。ロガーはログメッセージをコンソールに出力し、ログレベル(例: “debug”, “info”, “warn”, “error”)を管理します。

要求事項:

  1. ログレベルを設定するメソッド setLogLevel(level: string) を実装してください。
  2. ログメッセージを出力するメソッド log(message: string, level: string) を実装し、設定されたログレベルに応じてメッセージを出力するかどうかを制御します。
  3. ロガーは常に1つのインスタンスだけ存在するようにしてください。
class Logger {
  private static instance: Logger;
  private logLevel: string;

  private constructor() {
    this.logLevel = "info"; // デフォルトログレベル
  }

  // シングルトンインスタンスの取得
  public static getInstance(): Logger {
    if (!Logger.instance) {
      Logger.instance = new Logger();
    }
    return Logger.instance;
  }

  // ログレベルの設定
  public setLogLevel(level: string): void {
    this.logLevel = level;
  }

  // ログメッセージの出力
  public log(message: string, level: string): void {
    const levels = ["debug", "info", "warn", "error"];
    if (levels.indexOf(level) >= levels.indexOf(this.logLevel)) {
      console.log(`[${level.toUpperCase()}]: ${message}`);
    }
  }
}

課題: ログメッセージをファイルに出力する機能を追加し、コンソール出力とファイル出力を選択できるようにしてください。


演習3: データベース接続管理

データベース接続を管理するシングルトンクラスを作成してください。このクラスは、アプリケーション全体で1つのデータベース接続を管理し、必要なときにその接続を取得できるようにします。

要求事項:

  1. connect() メソッドを実装してデータベースに接続し、接続が確立されていない場合は新しい接続を作成してください。
  2. disconnect() メソッドを実装して接続を閉じる機能を追加してください。
  3. どの部分からでも同じ接続インスタンスを取得できるようにしてください。
class DatabaseConnection {
  private static instance: DatabaseConnection;
  private isConnected: boolean = false;

  private constructor() {}

  // シングルトンインスタンスの取得
  public static getInstance(): DatabaseConnection {
    if (!DatabaseConnection.instance) {
      DatabaseConnection.instance = new DatabaseConnection();
    }
    return DatabaseConnection.instance;
  }

  // データベースに接続
  public connect(): void {
    if (!this.isConnected) {
      console.log("データベースに接続しました。");
      this.isConnected = true;
    } else {
      console.log("既にデータベースに接続されています。");
    }
  }

  // データベース接続を切断
  public disconnect(): void {
    if (this.isConnected) {
      console.log("データベース接続を切断しました。");
      this.isConnected = false;
    } else {
      console.log("接続されていません。");
    }
  }
}

課題: 接続管理を強化し、接続プールを使って複数のクライアントが同時に接続できるようにクラスを拡張してください。


これらの演習を通じて、シングルトンパターンの応用が実際にどのように役立つかを理解し、実装力を高めてください。

まとめ

本記事では、TypeScriptでのシングルトンパターンの実装方法と、その応用について詳しく解説しました。静的プロパティと静的メソッドを活用することで、インスタンスの生成を制御し、効率的にリソースを管理できます。シングルトンパターンは、設定管理、ログ管理、データベース接続管理など、さまざまなシナリオで有用です。

実装時の注意点やアンチパターンにも触れ、適切なシングルトンの活用方法を理解していただけたと思います。シングルトンパターンを適切に使うことで、アプリケーションの安定性や保守性を向上させることができます。

コメント

コメントする

目次