January 19, 2026 (7mo ago) — last updated June 27, 2026 (1mo ago)

TypeScriptで学ぶシングルトン実装とテスト

TypeScriptでのシングルトン実装、利点・欠点、並行性やテスト対策、依存性注入による代替を実例と手順で解説します。

← Back to blog
Cover Image for TypeScriptで学ぶシングルトン実装とテスト

シングルトンは“唯一のインスタンス”を保証するパターンです。本記事ではTypeScriptでの安全な実装方法、利点と欠点、テストや並行性の注意点、そして依存性注入(DI)による実務的な代替手法を実例で示します。読み終える頃には、いつシングルトンを採用し、いつDIへ切り替えるべきか判断できるようになります。16

TypeScriptで学ぶシングルトン実装とテスト

王冠をかぶった威厳ある筆記官が、机の上の光る巻物を熱心に調べている鉛筆スケッチ。

シングルトンは“唯一のインスタンス”を保証するデザインパターンです。この記事ではTypeScriptでの安全な実装、利点・欠点、テスト性や並行性の注意点、そして依存性注入(DI)による現代的な代替手法を実例とともに解説します。シングルトンの基本と実務での扱い方を学び、必要な場面で適切に選べるようになります。1

TypeScriptはフロントエンドとバックエンドで広く採用されており、言語機能を活かした型安全な実装が可能です。導入事例や普及率の詳細は参考資料を参照してください。6


シングルトンとは何か、いつ有用か

シングルトンは、あるクラスのインスタンスがアプリケーション全体でただ一つだけ存在することを保証する設計です。これにより、設定やロギング、ハードウェアアクセスのように“単一の真実の情報源”を保てます。1

主な用途例:

  • ロギングサービス(すべてのイベントを統一された場所に記録)
  • アプリ設定の集中管理(設定の不整合を防ぐ)
  • ハードウェアインターフェース(デバイスへの競合コマンドを回避)

シングルトンはリソースの制御と一貫性を保証する強力な手段ですが、適用は慎重に行う必要があります。隠れた依存やテストのしづらさに注意してください。3


シングルトンの利点と欠点

利点

  • グローバルなアクセス点で使い回しが簡単
  • 遅延初期化で起動コストを抑えられる
  • 高コストなリソースの重複を防げる

欠点

  • 密結合になりやすくテストが困難になる
  • グローバルな可変状態はバグの原因になりやすい
  • 並行性の問題(競合状態)を招く可能性がある

ユニットテストやモジュール性を重視する場合、依存性注入(DI)を検討してください。DIにより依存を明示的に渡せるので、モックの注入やライフサイクル管理が容易になります。3


TypeScriptでの実装例 — 型安全なConfigManager

以下はTypeScriptでの典型的なシングルトン実装です。ポイントはprivateコンストラクタとstaticアクセサです(TypeScriptのクラス仕様の詳細は参照を参照)。2

class ConfigManager {
  private static instance: ConfigManager;
  private settings: Map<string, any> = new Map();

  private constructor() {
    console.log("Initializing ConfigManager instance...");
    this.settings.set("API_URL", "https://api.example.com");
    this.settings.set("TIMEOUT", 5000);
  }

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

  public get(key: string): any {
    return this.settings.get(key);
  }
}

使い方の例:

class ApiService {
  private apiUrl: string;

  constructor() {
    const config = ConfigManager.getInstance();
    this.apiUrl = config.get("API_URL");
    console.log(`ApiService initialized with API URL: ${this.apiUrl}`);
  }

  public fetchData(): void {
    console.log(`Fetching data from ${this.apiUrl}...`);
  }
}

console.log("Application starting...");
const service1 = new ApiService();
service1.fetchData();
const service2 = new ApiService();
console.log("Application finished.");

上の例では、ConfigManagerは一度だけ初期化され、複数のサービスが同一インスタンスを共有します。


なぜシングルトンには批判が多いのか

主な理由は“隠れた依存”と“テスト困難性”です。クラスが静かにグローバルインスタンスを参照すると、外部からは依存関係が見えなくなり、モックや差し替えが難しくなります。また並行処理環境では状態の競合が再現しづらいバグを生みます。こうした問題から、多くの現代的な設計ではDIを推奨します。3


シングルトンの代替:依存性注入(DI)とIoCコンテナ

DIは依存を明示的に渡すことでテスト性と柔軟性を高めます。下の例ではApiServiceがIConfigManagerというインターフェースを受け取ることで、テスト時にフェイクを注入できます。

interface IConfigManager {
  get(key: string): any;
}

class ApiService {
  private apiUrl: string;

  constructor(config: IConfigManager) {
    this.apiUrl = config.get("API_URL");
  }
}

フレームワークやライブラリ(例:NestJS、InversifyJS)はDIコンテナを提供し、トランジェントやスコープ付き、シングルトン風のライフサイクルを柔軟に管理できます。これにより“単一共有インスタンス”の利点を保ちながら、テスト性とモジュール性を改善できます。導入ガイドや具体例は社内のDI関連記事を参照してください(例: /articles/typescript-dependency-injection)。45


レガシーコードベースでの段階的リファクタ手順

  1. getInstance()呼び出しを洗い出し、責務を明確にする。
  2. 公開メソッドを列挙してインターフェースを定義する。
  3. 利用者を一つずつコンストラクタ注入に切り替える。
  4. 最終的にコンポジションルートまたはDIコンテナで単一インスタンスを配線する。

この段階的アプローチにより安定性を保ちつつ隠れた結合を減らせます。リファクタ時はテストを増やし、影響範囲を小さく保つことが重要です。


実務的な注意点

  • シングルトンは本当にユニークであるべきサービスに限定する。
  • 可能ならDIを優先して依存関係を明示化する。
  • 競合が起きやすい部分は同期化やスレッドセーフ設計を行う。
  • レガシー移行は段階的に、テストを増やしながら進める。

追加の短いQ&A(要点まとめ)

Q: シングルトンはいつ使うべきですか?

A: ロガーやハードウェアアダプタなど、真に単一であることが要件のサービスに限定してください。可能ならDIで管理する方が良いです。

Q: シングルトンがテストを難しくするのはなぜですか?

A: グローバルな可変状態がテスト間で漏れ、モック置換が難しくなり、テストの順序依存が生じるからです。

Q: レガシーからどう移行するべきですか?

A: getInstance()呼び出しを特定してインターフェース化し、利用者を段階的にコンストラクタ注入に切り替え、最後に配線をコンポジションルートで行います。


簡潔な3つのQ&A(よくある疑問に短く答える)

Q1: シングルトンと静的クラス、どちらを使うべき?

A1: 静的クラスは状態を持たないユーティリティ向け。インターフェースを渡して差し替えたい場合はシングルトンまたはDIを使います。

Q2: テストのための最小限の変更は?

A2: 関数やクラスが直接getInstance()を参照する代わりにインターフェースを追加し、テスト時はフェイクを注入します。

Q3: 並行処理での安全策は?

A3: 初期化の同期化、イミュータブルな状態保持、またはDIでスコープを分ける方法を検討してください。


1.
Martin Fowler, “Singleton,” Bliki, https://martinfowler.com/bliki/Singleton.html
2.
3.
Mark Seemann, “Singletons Are Pathological Liars,” https://blog.ploeh.dk/2010/07/28/SingletonsArePathologicalLiar/
4.
NestJS Documentation, “Dependency Injection,” https://docs.nestjs.com/fundamentals/injection
5.
InversifyJS Documentation, https://inversify.io/
6.
State of JS 2021, “TypeScript” (adoption data), https://2021.stateofjs.com/en-US/technologies/typescript/
← Back to blog
🙋🏻‍♂️

AIがコードを書きます。
あなたがそれを長持ちさせます。

AI加速の時代において、クリーンコードは単なる良い実践ではありません—スケールするシステムと自らの重みで崩壊するコードベースの違いです。