【デザインパターン】Singletonを学ぶ【C#】

C#

一言で言うと…

  • インスタンスを1つに制限し、どこからでもアクセスできるグローバルな窓口を提供する仕組み

概要

Singleton パターンは、GoF デザインパターンの一つで、生成に関するデザインパターンです。

クラスのインスタンスが 必ず1つしか存在しない ことを保証し、そのインスタンスへのグローバルなアクセス手段を提供します。

ゲーム開発においては、スコア管理・サウンド管理・設定管理など「アプリ全体で1つだけ存在すべきマネージャークラス」に頻繁に活用されます。 同じマネージャーのインスタンスが複数生成されてしまうと、スコアの二重管理や音の重複再生といった不具合の原因になります。Singleton はそれを防ぐための仕組みです。

構成

classDiagram
    class Singleton {
        - static _instance : Singleton
        - Singleton()
        + static Instance : Singleton
        + SomeOperation()
    }
    Singleton <.. Client : << use >>
要素役割
Singletonインスタンスを自身で管理し、唯一性を保証するクラス
ClientInstance プロパティ経由でアクセスする利用者

実装例(C#)

クラス概要

早速実装例です。今回はC#でゲーム開発を行う場合を想定してみました。

利用ケースは以下のように、ゲーム全体のスコアとゲームオーバー状態を管理するマネージャーです。

  • GameManager:Singleton クラス本体

ソースコード

1. Singleton クラス(GameManager)

C#
using System;

/// <summary>
/// ゲームマネージャー (Singleton)
/// </summary>
public class GameManager
{
    // 唯一のインスタンスを保持するフィールド
    private static GameManager _instance;

    // コンストラクタを private にして外部からの直接生成を禁止
    private GameManager()
    {
        Score = 0;
        IsGameOver = false;
    }

    /// <summary>
    /// 唯一のインスタンスを返すプロパティ
    /// 初回アクセス時に生成し、以降は同じインスタンスを返す
    /// </summary>
    public static GameManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new GameManager();
            }
            return _instance;
        }
    }

    public int Score { get; private set; }
    public bool IsGameOver { get; private set; }

    /// <summary>スコアを加算する</summary>
    public void AddScore(int points)
    {
        Score += points;
        Console.WriteLine($"スコア加算: +{points} → 現在のスコア: {Score}");
    }

    /// <summary>ゲームオーバー状態にする</summary>
    public void GameOver()
    {
        IsGameOver = true;
        Console.WriteLine($"ゲームオーバー!最終スコア: {Score}");
    }
}

コンストラクタを private にすることで new GameManager() による直接生成を禁止しています。 Instance プロパティが、インスタンスが未生成なら生成し、生成済みなら既存のものを返す「唯一の入口」になります。

2. クライアントコード

C#
class SingletonSample
{
    static void Main()
    {
        // シーンAでスコアを加算
        GameManager.Instance.AddScore(100);
        // => スコア加算: +100 → 現在のスコア: 100

        // シーンBでも同じインスタンスにアクセスできる
        GameManager.Instance.AddScore(200);
        // => スコア加算: +200 → 現在のスコア: 300

        GameManager.Instance.GameOver();
        // => ゲームオーバー!最終スコア: 300

        // 同一インスタンスであることを確認
        var a = GameManager.Instance;
        var b = GameManager.Instance;
        Console.WriteLine(ReferenceEquals(a, b)); // => True
    }
}

どのクラスからでも GameManager.Instance を通じて同一のインスタンスにアクセスできます。

補足:スレッドセーフな実装(Lazy<T>

マルチスレッド環境では上記のシンプルな実装だと複数インスタンスが生成される恐れがあります。 C# では Lazy<T> を使うとスレッドセーフかつシンプルに書けます。

C#
public class GameManager
{
    // Lazy<T> を使うとスレッドセーフな遅延初期化が簡単に実現できる
    private static readonly Lazy<GameManager> _lazy =
        new Lazy<GameManager>(() => new GameManager());

    public static GameManager Instance => _lazy.Value;

    private GameManager() { }
}

使いどころ

  • アプリ全体で共有すべき状態・リソースを管理したいとき
  • 複数のインスタンスが存在すると不整合が起きるクラスが必要なとき

ゲーム開発での具体例:

  • GameManager:スコア、ゲーム状態(プレイ中・ポーズ・ゲームオーバー)の管理
  • AudioManager:BGMやSEの再生制御(二重再生を防ぐ)
  • InputManager:入力状態の一元管理
  • SaveDataManager:セーブデータの読み書き

長所・短所

✅ 長所

  • インスタンスの一意性を保証できる
  • グローバルなアクセスポイントを提供でき、どのクラスからも参照しやすい
  • 遅延初期化(必要になったタイミングで生成)が可能なため、起動時のコストを抑えられる

⚠️ 短所

  • グローバルな状態を持つため、ユニットテストが難しくなる
  • 各クラスが直接 Instance を参照することで密結合になりやすい
  • 濫用すると「なんでもSingletonに入れるクラス」が生まれ、単一責任の原則に反しやすい
  • マルチスレッド環境での実装には注意が必要(Lazy<T> の使用を推奨)

他GoFパターンとの比較・関係

Factory Method / Abstract Factory との違い

Factory Method と Abstract Factory は「どんなオブジェクトをどうやって生成するか」にフォーカスしたパターンです。 一方 Singleton は「インスタンスの個数を1つに制限する」ことにフォーカスしています。

目的が異なるため、組み合わせて使うことも一般的です。 たとえば、GameManager.InstanceInstance プロパティ自体が Factory Method 的な役割を担っています。

パターン主な目的
Singletonインスタンスを1つに制限する
Factory Method生成するオブジェクトをサブクラスに委譲する
Abstract Factory関連するオブジェクト群をまとめて生成する

Builder との違い

Builder パターンは「複雑なオブジェクトを段階的に組み立てる」ためのパターンです。 Singleton は「1つのインスタンスを使い続ける」目的なので、用途が異なります。 Director クラスを Singleton として実装するケースもあります。

Facade との関係

Facade パターンは複雑なサブシステムへのシンプルな入口を提供するパターンです。 Singleton と組み合わせて「アプリ全体からアクセスできる Facade」を作るケースがよくあります。 ゲームの GameManager はまさにこの形になることが多いです。

まとめ

  • Singleton パターンは、インスタンスを1つに制限し、グローバルなアクセスポイントを提供する
  • コンストラクタを private にし、Instance プロパティで唯一のインスタンスを管理する
  • ゲーム開発での GameManagerAudioManager などのマネージャークラスに適している
  • マルチスレッド環境では Lazy<T> を使ったスレッドセーフな実装を検討する
  • 濫用するとテストしにくく密結合になるため、本当に唯一性が必要なクラスにのみ使う
  • Factory Method / Abstract Factory / Builder との組み合わせも一般的
タイトルとURLをコピーしました