C#でデザインパターン~Singletonパターン編~
2019-11-07
azblob://2022/11/11/eyecatch/2019-11-07-csharp-gof-singletonpattern-000.jpg

Singletonパターンとは?

C#でなるべくシンプルに実装するデザインパターン、今回はSingletonパターンについて解説したいと思います。
Singletonパターンの特徴は、Singletonという言葉の通りインスタンスが一つしか存在できないことです。
インスタンスが一つしか作られないと何がうれしいかというと、例えばログ取りを行う際などにログの内容を一時的にでもインスタンス内に貯めていく場合、どこで吐かれたログも一つのロギングを行うインスタンス内に貯めていくことができます。
もしログを取る際に毎回クラスをインスタンス化していると、インスタンスが分散して後でどこかにまとめなければならなくなります。
・・・ログを取るという用途に限って言えば.NETにはILoggerを使うという方法が一般的ではありますが、オブジェクトを無二のものにしておきたいというユースケースは時々発生します。
実装自体はクラス1個に細工をする程度の規模なので、気軽にコードを見て仕組みを見てみましょう。

とにかく実装です!実装を見せてください!

とに実です。何でもありません。
Singletonパターンを実装するのはこのSingletonクラス一つです。
注目するべきなのは、コンストラクタ(private SingletonClass(string str)の部分)のアクセス修飾子がprivateに設定されていることです。
これによって、クラスのインスタンス化を外部から行えないよ言うにすることにより、このクラスはこのコードの世界で唯一のインスタンスになることができます。
では、このクラスはどうやってインスタンス化されるのかというと、GetInstance()メソッドがそれにあたります。
クラス内にはsingletonClassという自身のインスタンスを保持しておくプロパティがあり、GetInstance()メソッドが初めて呼ばれた際にはインスタンス化を行ってから、そのインスタンスを返します。
そして、このメソッドが2回以上呼ばれた際は既にインスタンスが保持されているので、それを返します。
このメソッドの用途は、クラスのインスタンス化と、そのクラスのインスタンスの取得になります。
また、GetInstance()メソッドはstaticなメソッドとして作っているので、このクラス自体をインスタンス化しなくても利用することができます。

class SingletonClass
{
    private static SingletonClass singletonClass;
    private string description;

    private SingletonClass(string str)
    {
        description = str;
    }

    public static SingletonClass GetInstance()
    {
        if (singletonClass == null)
        {
            Console.WriteLine("初期インスタンス作成につき説明を入力してください");
            var str = Console.ReadLine();
            singletonClass = new SingletonClass(str);
            return singletonClass;
        }
        return singletonClass;
    }
    public string GetDescription()
    {
        return "このクラスの説明は:" + description;
    }
}

今回実装したクラスを実際に使うコードを書きます。
先ほども解説したとおり、GetInstance()メソッドを使うことでインスタンスを取得できます。
GetDescription()を使うことでインスタンス化時に作成したDescriptionを読み出すことができます。
同様にして、もう一度GetInstance()を呼び出そうとするとどうなるのでしょうか?
それについて知るにはまずこのコードを実行してみましょう。

class Program
{
    static void Main(string[] args)
    {
        var singleton = SingletonClass.GetInstance();
        Console.WriteLine(singleton.GetDescription());
        var anotherSingleton = SingletonClass.GetInstance();
        Console.WriteLine(anotherSingleton.GetDescription());
    }
}

ここまでのコーディングが上手くいっていれば、コードを走らせると以下のように出力されるはずです。
2行目は、ReadLineによる入力の受付になります。
3行目はsingleton.GetDescription()による出力ですが、4行目はanotherSingleton.GetDescription()による出力です。
3行目と4行目の間では同じようにSingletonClass.GetInstance()を使用していますが、今回は説明文の入力を求められていません。
これがGetInstance()内で指定した、既にインスタンスが作られているなら再度インスタンスを生成しないという振舞いになります。
これによって、別の場所で同じようにインスタンスを入手しようとしても既に存在するインスタンスを受け取ることになり、実行空間上で唯一のインスタンスになることを保証してくれます。

初期インスタンス作成につき説明を入力してください
説明です
このクラスの説明は:説明です
このクラスの説明は:説明です

おわりに

今回は、デザインパターンの一つのSingletonパターンをシンプルに実装しました。
この実装は本当にシンプルな形なので、実際に使用する際はこのGetInstance()メソッドを複数のスレッドで同時に呼ばれた際の処理についても考えておかなければなりません。
下手をすると、ほとんど同時にメソッドが実行されることによってどちらにおいても現状インスタンスが存在しないと判断され、複数のインスタンスが生成される恐れもあります。
この点についても、ゆくゆく考察してみたいと思います。