シングルトンは“唯一のインスタンス”を保証するパターンです。本記事ではTypeScriptでの安全な実装方法、利点と欠点、テストや並行性の注意点、そして依存性注入(DI)による実務的な代替手法を実例で示します。読み終える頃には、いつシングルトンを採用し、いつDIへ切り替えるべきか判断できるようになります。16
January 19, 2026 (7mo ago) — last updated June 27, 2026 (1mo ago)
TypeScriptで学ぶシングルトン実装とテスト
TypeScriptでのシングルトン実装、利点・欠点、並行性やテスト対策、依存性注入による代替を実例と手順で解説します。
← Back to blog
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
レガシーコードベースでの段階的リファクタ手順
- getInstance()呼び出しを洗い出し、責務を明確にする。
- 公開メソッドを列挙してインターフェースを定義する。
- 利用者を一つずつコンストラクタ注入に切り替える。
- 最終的にコンポジションルートまたはDIコンテナで単一インスタンスを配線する。
この段階的アプローチにより安定性を保ちつつ隠れた結合を減らせます。リファクタ時はテストを増やし、影響範囲を小さく保つことが重要です。
実務的な注意点
- シングルトンは本当にユニークであるべきサービスに限定する。
- 可能ならDIを優先して依存関係を明示化する。
- 競合が起きやすい部分は同期化やスレッドセーフ設計を行う。
- レガシー移行は段階的に、テストを増やしながら進める。
追加の短いQ&A(要点まとめ)
Q: シングルトンはいつ使うべきですか?
A: ロガーやハードウェアアダプタなど、真に単一であることが要件のサービスに限定してください。可能ならDIで管理する方が良いです。
Q: シングルトンがテストを難しくするのはなぜですか?
A: グローバルな可変状態がテスト間で漏れ、モック置換が難しくなり、テストの順序依存が生じるからです。
Q: レガシーからどう移行するべきですか?
A: getInstance()呼び出しを特定してインターフェース化し、利用者を段階的にコンストラクタ注入に切り替え、最後に配線をコンポジションルートで行います。
簡潔な3つのQ&A(よくある疑問に短く答える)
Q1: シングルトンと静的クラス、どちらを使うべき?
A1: 静的クラスは状態を持たないユーティリティ向け。インターフェースを渡して差し替えたい場合はシングルトンまたはDIを使います。
Q2: テストのための最小限の変更は?
A2: 関数やクラスが直接getInstance()を参照する代わりにインターフェースを追加し、テスト時はフェイクを注入します。
Q3: 並行処理での安全策は?
A3: 初期化の同期化、イミュータブルな状態保持、またはDIでスコープを分ける方法を検討してください。
AIがコードを書きます。あなたがそれを長持ちさせます。
AI加速の時代において、クリーンコードは単なる良い実践ではありません—スケールするシステムと自らの重みで崩壊するコードベースの違いです。