シングルトンは“唯一のインスタンス”を保証するデザインパターンです。本記事ではTypeScriptでの安全な実装、利点・欠点、テスト性や並行性の注意点、そして依存性注入による現代的な代替手法を実例とともに解説します。
January 19, 2026 (3mo ago) — last updated February 28, 2026 (2mo ago)
TypeScriptで学ぶシングルトン徹底ガイド
TypeScriptでのシングルトン実装と注意点を解説。メリット・デメリット、テストや並行性の対処法、依存性注入による代替までを実例で学べます。
← Back to blog
TypeScriptで学ぶシングルトン徹底ガイド

ソフトウェア開発では便利だが注意深く扱うべき道具がいくつかあります。シングルトンはその代表格で、クラスから生成されるインスタンスをただ一つに制限し、どこからでもアクセスできる単一の接点を提供します。こうした管理は設定やロギング、ハードウェアアクセスのように“唯一であるべき”リソースに対して重要です。シングルトンの基本概念と実装、注意点、そしてよりテストしやすい代替手段までをこのガイドでまとめます。1
シングルトンとは何か、いつ有用か
中世の王国で公式の王立筆記官が一人だけいるように、シングルトンは特定の役割に対して唯一のインスタンスを強制します。これにより、設定やログなどの“単一の真実の情報源”を保てます。
主な用途例:
- ロギングサービス(すべてのイベントを統一された場所に記録)
- アプリ設定の集中管理(設定の不整合を防ぐ)
- ハードウェアインターフェース(デバイスへの競合コマンドを回避)
シングルトンは単なる便利機能ではなく、リソースの制御と一貫性を保証するデザイン手法です。ただし適用は慎重に行ってください。1
シングルトンのメリットとデメリット
利点
- グローバルなアクセス点で使い回しが簡単
- 遅延初期化により起動コストを抑えられる
- 高コストなリソースの重複を防げる
欠点
- 密結合になりやすくテストが困難になる
- グローバルな可変状態はバグの原因になる
- 並行性の問題(競合状態)を招く可能性がある
ユニットテストやモジュール性を重視するチームでは、依存性注入(DI)などの手法が推奨されることが多いです。3
TypeScriptでの実装例 — 型安全なConfigManager
以下はTypeScriptでの典型的なシングルトン実装です。ポイントはprivateコンストラクタとstaticアクセサです。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コンテナを提供し、トランジェントやスコープ付き、シングルトン風のライフサイクルを柔軟に管理できます。これにより“単一共有インスタンス”の利点を失わずに、テスト性とモジュール性を大幅に改善できます。45
レガシーコードベースでの段階的リファクタ手順
getInstance()呼び出しを洗い出し、責務を明確にする。- 公開メソッドを列挙してインターフェースを定義する。
- 利用者を一つずつコンストラクタ注入に切り替える。
- 最終的にコンポジションルートまたはDIコンテナで単一インスタンスを配線する。
この段階的アプローチにより安定性を保ちつつ隠れた結合を減らせます。
実務的な注意点
- シングルトンは最小限かつ本当にユニークなサービスに限定して使う。
- 可能ならDIを優先して明示的な依存関係にする。
- 競合が起きやすい部分は同期化やスレッドセーフ設計を行う。
- レガシー移行は段階的に、テストを増やしながら進める。
追加の短いQ&A(要点まとめ)
Q: シングルトンはいつ使うべきですか?
A: ロガーやハードウェアアダプタなど、真に単一であることが要件のサービスに限定してください。可能ならDIで管理する方が良いです。
Q: シングルトンがテストを難しくするのはなぜですか?
A: グローバルな可変状態がテスト間で漏れ、モック置換が難しくなり、テストの順序依存が生じるからです。
Q: レガシーからどう移行するべきですか?
A: getInstance()呼び出しを特定してインターフェース化し、利用者を段階的にコンストラクタ注入に切り替え、最後に配線をコンポジションルートで行います。
よくある質問(簡潔なQ&A)
Q: シングルトンは常に悪い選択ですか?
A: いいえ。本当にユニークなサービスには適切です。しかし多くの場合はDIの方が長期的に優れています。
Q: 静的クラスとシングルトンの違いは?
A: 静的クラスはインスタンスを持たず静的メンバーのみです。シングルトンは一つのインスタンスを持ち、インターフェースを実装して渡せます。
Q: テストしやすい設計にするには?
A: 依存をコンストラクタで受け取り、インターフェースを使ってモックを注入できるようにします。
AIがコードを書きます。あなたがそれを長持ちさせます。
AI加速の時代において、クリーンコードは単なる良い実践ではありません—スケールするシステムと自らの重みで崩壊するコードベースの違いです。