Singletonパターンは使わなくてもいい?
Singletonパターンに関するポエムです。
長文です。
Singletonパターン
GOFデザインパターンの中でも最もわかりやすのがSingletonパターンです。
初心者が一番理解しやすいパターンだと思います。
私自身、初めてデザインパターンの本を読んだとき、唯一理解できたのがSingletonパターンでした。
疑問
そんな思い出深いSingletonパターンでですが実践においては使ったことがなく、どういう場面で使えばよいのかずっと疑問でした。
そんなに大きなメリットある?
「システムに一つしかないことを保証したいリソースを抽象化、管理するためにSingletonにする」という指針はよく耳にします。これはある意味では納得します。一方でそれは一般的に悪とされるグローバル変数です。グローバル変数化するデメリットをSingleton化するメリットが上回るのでしょうが、そのような比較検討を見たことがありません。
Singletonには超すごーいメリットがあるのでしょうか?それともグローバル変数って巷で言われているほど悪くはないのでしょうか?
他の選択肢に対する優位性は?
「システムに一つしかないことを保証したいリソースを抽象化、管理する」ならSingleton以外の実現方式もあります。例えばスレッドプールのように決まった数のリソースをプールして要求に応じて払い出すようなクラスを作成すれば実現できます。この方が汎用性は高いと思います。複数ある実現方式の中でなぜSingletonじゃないといけないのでしょうか?
「システム内に1つで十分なクラス」ってほとんどそうじゃない?
「システム内に1つで十分なクラスはSingletonにする」という指針もよく目にします。これを徹底するとシステム内の大多数のクラスがSingletonになる気がします。「複数あっても問題ないけど、このシステムの要件だと1つで十分」というクラスは相当数あるはずです。
でもSingletonだらけのシステムなんて見たことがありません(ネットの噂だと存在するらしいですが)。
それにそんな風に作られたシステムのコードの再利用性は低く、設計としていかがなものか思います。
どこまでSingleton化するか線引するためのもう一段階細かい基準があるのだと思うのですがそれがわかりません。
SOLID原則から見ると歪んでない?
私の行動指針であるSOLID原則の1つ、Single Responsibility Principleに照らし合わせるとSingletonパターンはやりすぎに見えます。あるリソースがあったとき、そのリソースを1つだけ使いたいアクターもいれば複数使いたいアクターもいるはずです。前述のスレッドプールのように責務を分割しないと片方のアクターの要求がもう片方のアクターに影響を及ぼす設計になります。
Singletonを強制するユニットテストはあるか?
私はTDDっぽくテストから設計を導出する(事前に設計するスキルがないから)のですが、今まで一度もSingletonパターンを使ったことがありません。
私のTDDのやり方が間違っている可能性はあります。脱線しそうですが多分、間違ってます。でもSingletonパターン以外の実装選択肢がないテストケースってどうやったらTDDの流れの中で出てくるんでしょうか?
私の拙い経験では他の選択肢を排除できず、SRPの観点からSingleton以外の方式を選択してました。予めSingletonを使うと決めてないと無理じゃないかと思ったりします。
その割に周りでよく耳にするのはなぜ?
決定的とは言えないまでもマイナス要素ばかりが目について、逆に積極的に採用する理由を見いだせないのですが、「ここSingletonにする?」みたいなプログラマー同士の会話はよく耳にします。特にファクトリー周りの話ではよく出てくる気がします。私だけが気づいていないすごーいメリットがSingletonにはあるのでしょうか?全然、会話に入っていけません。
その割に周りでよく目にするのはなぜ?
コード上でも時折目にします。その度にSingleton化せざるを得なかった理由を足りない脳みそで考えるのですがなにも思いつきません。
私だけがアホなの?
確かにアホです。でも私だけがわかっていないSingletonのいい所ってなんなんでしょう?皆さん、どうやってSingletonの適用要否を判断してるんでしょう?
参考になる記事発見
なにか(忘れた)調べてたら偶然、この記事を見つけました。
Singletonパターンの悪いところが割と具体的に書かれてます。
長い間、抱えてきた疑問が解消する期待が膨らんで参考サイトまで含めて何度も熟読してしまいました。
これらを読んで得た感想としては、「Singletonパターンが必須になる状況ってないんだな」ということです。
考察
そんなに大きなメリットある?
グローバル変数化に対する純粋な正当性はないっぽいですね。
プログラマ個々人の価値観としてグローバル変数を便利な必要悪として許容するタイプか、絶対拒絶するタイプかで判断が変わるだけのようです。
他の選択肢に対する優位性は?
これもないっぽいですね。
設計原則やTDDを重視する人たちはSRPやTDDとの相性からむしろSingleton以外の選択肢を推奨してます。
「システム内に1つで十分なクラス」ってほとんどそうじゃない?
Singletonを利用する人の中でも「システム内に1つで十分なクラス」をどこまでSingleton化するかはわかれるようです。本当に大半のクラスをSingleton化してしまう人もいれば、著名なパターン本でSingletonの利用が推奨されている範囲内に留める人もいます。プログラマー個々人の属人的な判断になっているのが実情のようです。
属人的な判断をする上での共通的な指針すら揺らいでいる状況です。GOFのエリック・ガンマは「Singletonパターンをデザインパターンから取り除きたい」と後悔しているそうです。これをもってGOFのデザインパターン本のSingletonパターンの記述は無効だと考える人たちもいます。
それでも書籍通りに解釈して広い範囲で利用するか、著名なパターン本で明示的に強く推奨されている範囲内に留めるか、使わないか、完全に個々人の価値観に委ねられています。
SOLID原則から見ると歪んでない?
責務を分割すべきだと主張している人も多いです。
Singletonを強制するユニットテストはあるか?
ここについてはまだ答えを得られてません。
後述しますがユニットテストとの相性が悪いので、TDDの中では意識的にSingletonを避ける人もいるようです。
その割に周りでよく耳にするのはなぜ?
やはり著名な本に記載されているからなのかなと思いました。 例えばGOFのデザインパターン本のAbstract Factoryの項では以下のように記載されています。
典型的なアプリケーションでは、部品の集合ごとにConcreteFactoryクラスのインスタンスを1つしか必要としない。したがって、通常ではConcreteFactoryクラスをSingletonパターンを使って実装することが最良の方法になる。
エリック・ガンマのコメントを知っていたとしても、全てのプログラマの聖典にこのように明記されていたら一応、相手に確認して意識合わせするのが自然な流れだと思います。
エンタープライズシステム関連の著名なデザインパターン本やアーキテクチャパターン本でもSingletonパターンと組み合わせることを勧めていることがあります。もしそのような書籍からパターンを採用する場合もSingleton化について意識合わせするのは自然な流れだと思います。
その割に周りでよく目にするのはなぜ?
その意識合わせの議論の中に、Singleton肯定派がいて、Singleton否定派が戦わなくて、Singleton化することが採択された結果なのでしょう。著名な本に書かれているという事実は大きいです。「長いものには巻かれろ」は人間の普遍的心理です。
もしくは議論自体なく、実装者の一存で実装されてコードレビューで見つけたけど、今更指摘しても遅いからとスルーされたのかもしれません。昨今のアジャイルな流れの中では事前にコーディング規約などで禁止しておかないとSingletonは防げないと思います。しかしそこまでしているプロジェクトはほとんどないのではないでしょうか。
その他の批判
マルチスレッド環境ではアクセス管理(同期)が必要で、実装負荷が増えます。
ユニットテストと相性が悪いと言われています。Singletonのフィールドの値はテスト間でクリアされないため先発のテスト内容が後続のテストに影響を与えてしまうからです。これはSingletonだけでなくSingletonを利用するクラスのテストも難しくします。フィールドの初期化処理をいれれば解決しますが、そこまでするメリットがないと感じるようです。
クラス名さえ知っていればどこからでも呼べるため、クラスの階層設計を無視してどこからでも呼び出せる飛び道具になります。多用するとクラス間の依存関係がぐちゃぐちゃになってしまいます。これはSingletonだけでなくStaticメソッドを提供しているクラス全般に言えることです。クラスは与えられた(Injectされた)クラス以外は利用すべきではないと考える人にとっては悪です。
感想
「使ってもいいけどSingletonパターンが必須不可欠になる状況はない」と感じました。使いたくなければ使わなくも全然、大丈夫っぽいです。
一方、「著名なXXX本にこのパターンとの組み合わせが推奨されているから」という理由で使う人たちがいるということを認識する必要があると感じました。
使いどころ
逆にSingletonパターンを使ってよい状況というのはどういう状況でしょう?
こんな指針を見つけました。
状態を持たないこと
- 状態を持つとグローバル変数化する
- 「システムに一つしかないDBセッションをSingletonで管理」とかはNG例
ポリモーフィズムが絡む (抽象クラスまたはインタフェースを実装している) こと
- Interfaceを実装せず、状態も持たないならただのUtilityクラスでよい
- Interfaceを継承する子クラスの中で状態を持たないクラスはSingletonにする
- Interfaceを継承する子クラスの中で状態を持つクラスは普通のクラスにする
SingletonはInjectionする
利用メソッド側でgetInstanceをハードコードすると試験容易性が落ちる
適したケース
- Strategyパターンとは相性がよい
- Strategyの実装クラスは状態を持たない
その他の適したケース(怪しい)
- ロギング
- キャッシュ管理
- スレッドプール管理
- データベース接続ドライバ
- ソケット制御ドライバ