Singletonパターン再考 staticクラスはSingletonパターンに入りますか?
2019-11-18
azblob://2022/11/11/eyecatch/2019-11-18-csharp-singletonpattern-ststic-000.jpg

はじめに

以前、Singletonパターンについての記事を書きました。
基本的にこの「C#でデザインパターン」シリーズでは、デザインパターンの単純な実装例を示すところまでを主眼において一般論的な内容の記事を書いています。
しかし、そのユースケースや周辺の論点を調べているとそれらについて自分でも感じたり考えたりすることがあります。
今回はそのようなデザインパターンを学んでいるうちに感じたよしなしごとを書いてみようと思います。

Singletonパターンの周りの話題

Singletonパターンについて調べていると大抵2つか3つの話題に行き当たります。
一つはスレッドセーフなSingletonパターンの実装の話、次の一つはSingletonパターンは実はアンチパターンであるというお話、そして最後の一つはSingletonパターンはstaticなクラスと何が違うのかというお話です。
これらのうち、スレッドセーフなSingletonパターンの実装のお話とSingletonパターンの問題点のお話は調べるとそれなりに納得でき、「そういった実装ができるんだなぁ」とか「確かにそういわれると実際に使える場面って少ないよね」と納得できてしまいました。
しかし、最後のstaticなクラスとSingletonの話については、少し気になる部分がありました。
staticクラスとSingletonのクラスの違いは何かという問題において、staticクラスはこうでSingletonなクラスはこうでといった違いはわかるのですが、それではstaticクラスはSingletonパターンには当てはまるのでしょうか、当てはまらないのでしょうか。
この点については議論している例が見当たらなかったのですが、自分の中で「ひょっとしたらstaticクラスはSingletonパターンに当てはまるのではないか・・・?」と思うようになりました。
今回は、そんなstaticクラスとSingletonなクラスの違いについて纏めなおした上で、デザインパターンのユースケースから考えるとstaticクラスもSingletonパターンなのではと一瞬思ったお話について書いていきます。
話の主題は後半部分なので、staticなクラスとSingletonなクラスの違いについて一通り分かっている方は前半部分は飛ばしつつ読んでいただけると幸いです。

staticなクラスとSingletonなクラスの違い

staticなクラスを用いたSingletonと元々のSingletonなクラスの実装はそれぞれこのようなものです。

  • staticなクラス
static class StaticClass
{
    static public void DoSomething()
    {
        Console.WriteLine("Doing something...");
    }
}
  • Singletonなクラス
    こちらは、前回の記事のコードよりスレッドセーフに作ってあります。
class SingletonClass
{
    static private SingletonClass _instance = new SingletonClass();
    private SingletonClass()
    { 
    }
    static public SingletonClass GetInstance()
    {
        return _instance;
    }
    public void DoSomething()
    {
        Console.WriteLine("Doing something...");
    }
}

これらはどちらも、クラスのインスタンスは一つしか作られないことが保証されています。
staticクラスの場合はインスタンスはそのクラスの呼び出し元のプログラムが読み込まれる際に一つだけ作られてそれ以降インスタンス生成を行わずに呼び出せることによって唯一であることが保証されます。
Singletonなクラスの方では初めてそのクラスが呼ばれた際に初期化子によってインスタンスが作られ、それ以外の方法で外部からprivateのコンストラクタが呼べないことによって唯一性が保証されています。
これらの違いとして挙げられていることとしては主に二つあります。

  • クラスのインスタンス化が行われるタイミング
  • 継承の可否
    クラスのインスタンス化が行われるタイミングとしては先ほど解説したとおりstaticなクラスはそのクラスの呼び出し元のプログラムが読み込まれる際、Singletonなクラスはそのクラスで初めてGetInstance()された時にインスタンスが作成されます。
    結果として、staticなクラスの方がライフタイムが長くなり、より長い間メモリの中に居座り続けます。
    一方でSingletonなクラスのインスタンスは必要になってから呼ばれるのでより無駄が少なくなります。
    また、継承の可否という観点で見ると、staticなクラスはシールされているためObject以外のものを継承したり、あるいは継承元クラスとして使うことが防止されています。
    一方で、Singletonなクラスは、継承を利用することで振舞いを継承した別クラスを作成して使用することができます。
    こうして見ると、Singletonなクラスの方はSingletonパターンに従ったコードを作成する目的においてどちらの面においても一枚上回っているように見えます。
    ゆえに、唯一のオブジェクトを作って使いたいというSingletonパターンのユースケースにおいてはstaticではなくSingletonな形でクラスを作りましょう、というのが大体の場合結論になります。

デザインパターンのユースケースから考えるとstaticクラスもSingletonパターンなのではと一瞬思ったお話

しかし、これらはどちらもSingletonパターンの本来の目的であるインスタンスの唯一性を保証する手段としては満たしているのではないかと感じます。
実際、オブジェクト指向における再利用のためのデザインパターンにおいては、Singletonパターンの目的は

あるクラスに対してインスタンスが1つしか存在しないことを保証し、それにアクセスするためのグローバルな方法を提供する。

とあります。
また、Singletonパターンが批判される要因としてよく挙げられる「実質的にグローバル変数になってしまう」という問題を解決しようとすると、Singletonに対して持たせる役割としては複数の静的なメソッドの詰め合わせのようなものになります。
静的クラスと静的クラス メンバーによると、

静的クラスは、入力パラメーターに対してのみ処理を行い、内部のインスタンス フィールドを取得したり設定したりする必要のない一連のメソッドを格納する、便利なコンテナーとして使用できます。

とあり、これはstaticクラスの使いどころと一致しているように思えます。
これらのことから、SingletonなクラスはSingletonパターンを実践するための実際のコードの唯一の形ではなく、staticなクラスもSingletonパターンに一部含まれているといっていいのではないかと考えました。
継承が使えない、よりライフタイムが長いという欠点はありますが、記述はよりシンプルに済み、メソッド群のコンテナーという面でいうとより的確にその体をなしています。
そこまで自由度を高くする必要がなければ、Singletonパターンの実装としてstaticクラスを使用するのも一つの方法なのではないでしょうか。

・・・・・・と思ったらオブジェクト指向における再利用のためのデザインパターンのSingletonパターンの適用可能性の欄にこのようなものがありました。

唯一のインスタンスがサブクラス化により拡張可能で、また、クライアントが、拡張されたインスタンスをコードの修正なしに利用できるようにしたい場合。p137

Singletonパターンを使う場合のユースケースとしては、何かしらの振る舞いを継承した複数のクラスをそれぞれSingletonにすることで、それらをそれぞれ唯一に保ちつつ、実装を作り分けるというような場合も考えているようです。
こうなると、やはりstaticなクラスだけではSingletonパターンで求められることのインスタンスを唯一に保つことはできても、それをサブクラス化によって拡張したりすることには対応できません。
ここから得られる結論としては、「staticなクラスとSingletonなクラスは何が違うの?」という問いに対して、よりデザインパターンの定義に沿った回答をするなら、「staticなクラスは継承が行えないためSingletonパターンで求められるようなサブクラスごとに実装を作り分ける柔軟性が持てない」といえそうです。
最初に考えていたほどには衝撃的な結論ではないですが、Singletonパターンについていくらか見識が深まったように思えます。

おわりに

ところで、問題はというとそのように使う場面が実際にあるのかという点です。
例えば、それぞれ違うDBMSを使うDBへのCRUD操作を行う際に振舞いとしてはどれも特定のCRUD操作を行いたい、けれどもDBアクセスを行うクラスはそれぞれ二つ以上いらないし中に変数を持っておいてプログラムの実行中に書き換えを行う必要もない・・・そんな時に使うのでしょうか。
今はそんなSingletonパターンを使用している実例について色々調べているのですが、次回はそれらについて書いていきたいと思いますので、よろしくお願いします。

謝辞

今回の記事作成にあたって、若月さんにアドバイスを頂きました。
私の記事を読んで話題に出してくださり、アドバイスを頂けたことは記事を書いていくモチベーションに大きく繋がりました。
ありがとうございました!