デザインパターンの1種として「シングルトンパターン」というものがあります。
シングルトンパターンとは、インスタンスが1個しか生成されないことを保証するデザインパターンことですが、このシングルトンパターンの使い所はどこ?staticとの違いはなに?とわからないことが多かったので調べてみました。
目次
インスタンスが1個しか生成されないことを保証するというものがイメージできないと思うので、具体的なコードを見てみます。
class Singleton {
// インスタンス化されているか判断されているかに使用するメンバ変数
private static $instance;
// コンストラクタをprivateにして外部からアクセスできないようにする
private function __construct(){
echo "インスタンスを生成したよ";
}
// すでに自クラスがインスタンス化されているかを判断する
public static function getInstance() {
if ( !isset(self::$instance) ) {
self::$instance = new Singleton();
}
return self::$instance
}
}
シングルトンパターンを実装したいときに必要なのはたったの3つのステップです。
- インスタンス化されているかを判断するための変数を用意
- private コンストラクタを実装
- 外部に公開するためのgetInstanceメソッドの中に,自クラスがインスタンス化されていない時だけ新たにインスタンス化するメソッドを実装
この3つのステップを踏めばシングルトンパターンの実装ができます。
ちなみに呼び出したいときは,常にgetInstanceを呼ぶことで一度インスタンス化したクラスを使用できます。
// $singleton1と$singleton2は同じインスタンスが返っている
$singleton1 = Singleton::getInstance();
$singleton2 = Singleton::getInstance();
これが基本的なシングルトンパターンの実装方法です。
シングルトンパターンとstaticでは一体何が異なるのかわからなかったので調べてたことをまとめます。
主な違いは下記の2つです。
- ただ一つインスタンス化すること保証する
- 継承やインターフェースを実装できる
ただ一つインスタンス化すること保証する
たくさんのクラスが複雑に継承してある場合、無駄なインスタンス化によって予期しないところでバグやエラーが発生することがあります。
例えば、DBにアクセスするためのクラスを各自が自由にインスタンス化して変更したとすると各インスタンス間でDBアクセスをする際にオブジェクトの状態が異なることから不整合が生じる可能性があります。
それを阻止するために、コメントなどで「このクラスはインスタンス化しないこと」と記述しルールを設けたとしてもそれに気づかなかった開発者がそのクラスをインスタンス化してしまう可能性があります。
しかし、シングルトンパターンを使えば1度インスタンス化されたオブジェクトは再度インスタンス化されることを防ぎ、常に同じオブジェクトを使い回すことが保証されます。
継承やインターフェースを実装できる
シングルトンパターンはクラスを使うことで実現できるデザインパターンのため、継承やインターフェースを実装することができます。
これはシステムが大きくなればなるほど威力を発揮します。
継承やインターフェースを用いることでクラスの依存関係を管理することができるので拡張性も広がり、開発の際のの選択肢が増える場合があります。
シングルトンパターンを調べている上で、できることなら使うな、使う場合は十分注意せよと警鐘を鳴らしている記事を多数見かけました。
その記事を見てみると主に以下が挙げられていました。
- 拡張性に問題が出る可能性がある
- ユニットテストがやりにくくなる
- マルチスレッド環境に導入する場合はアクセス管理が必要
- 多くの状態(メンバ変数)を持つクラスには不向き
拡張性に問題が出る可能性がある
上記ででシングルトンパターンを用いることで,継承やインターフェースを用いることで拡張性が広がる場合があると書きましたが、一方で狭まってしまう場合もあり得ます。
その原因となるのが将来的にそのクラスが本当に1つのインスタンスを生成することで役割を果たせるのか予測が困難ということです。
理由として考えられるのは、サービスは大きくなっていくにつれてビジネス側の要求も変わっていくからです。
最初はそのクラスは単一の責任を果たすだけでよく、特に柔軟性が求められない場合は問題がないですが、そこに柔軟性が求められるようになってきた場合にそのクラスがシングルトンパターンで実装されていることで身動きができなくなるということが考えられます。
ユニットテストがやりにくい
ユニットテストを行う際はクラス間が疎結合であることが前提になっています。つまり、クラス間に依存関係がない状態がユニットテストを行う前提となるということです。
この疎結合でない状態をシングルトンパターンで作り出してしまう危険性があります。
同じオブジェクトを使い回す場合、当たり前ですが、オブジェクトの状態も引き継いでいます。
例えば、ある地点でオブジェクトの状態が変化している場合にその地点の前と後を考慮してテストを記述しなければならず、不安定なユニットテストが生まれてしまいます。
このようにシングルトンパターンを採用するとオブジェクトを使い回すという性質上、理論的には独立しているはずのクラス間の関係に状態を受け継ぎ、暗黙的な依存関係が生まれてしまうことで、ユニットテストがやりにくくなってしまいます。
マルチスレッド環境に導入する場合はアクセス管理が必要
マルチスレッド環境に導入する場合には、ロックなどのアクセス管理が必要になります。
ロックを用いる状態を作ってしまうことで、そもそもマルチスレッドという不安定なプログラミング手法の中にデットロックなどの問題を起こす可能性のある記述を増やしてしまいます。
そして、並列処理にして処理速度向上を望んでいる中で、ロックを用いることでそれ事態がコストになってしまい処理速度が遅くなってしまうという問題と向き合わなくてはいけなくなります。
多くの状態(メンバ変数)を持つクラスには不向き
シングルトンパターンの性質上同じオブジェクトを複数箇所で使うことになります。
これは言い換えると、その中に含まれるメンバ変数などはグローバル変数と変わらないということになります。
エンジニアにとってどこからでもアクセスし、変更できるグローバル変数はバグの温床になることはすでにご存知だと思います。
この多くの状態をもつシングルトンパターンでsetterなどのアクセスメソッドを実装してしまった場合には今後の開発で技術的負債になる可能性が極めて高いと言えます。
そのため、シングルトンパターンを採用するクラスにはなるべく状態を持たないように実装するべきです。
特にユーザー情報などをもつクラスは間違ってもシングルトンパターンで実装しないように注意が必要です。
ここまででシングルトンパターンは,使い所を間違えると将来的に致命的な技術的負債になり得るということがわかってきました。
そこで、実際にシングルトンパターンをどのタイミングで使うべきなのかをまとめます。
今まで学んだことから考えると以下の条件を満たす場合にのみシングルトンパターンを使うべきであると考えられます。
- 【動的なクラスでない場合】
→引数やアクセス修飾子などにより,値が変化することが考えられる場合は将来的に一つしかインスタンス化できない状況が足枷になることがあり得る - 【そのクラスがどのアプリケーションに呼び出されるか意識しない場合】
→そのクラス自身が他のクラスなどに依存せずに,呼び出され先を意識しないで済むかを考える必要がある - 【継承やインターフェースをしたり、されたりするか】
→staticではできないこと,シングルトンパターンの特徴の一つ - 【他のアプリケーションは常にこのクラスの1つのインスタンスを必要としているか】 →1つのインスタンスしか生成できないことが将来的にデメリットになるため、本当にそのクラスが一つのインスタンスを生成するだけで望まれる役割を果たすかを考える必要がある
上記の場合を全て満たす場合にのみシングルトンパターンを適用する意味があるといえます。
まとめ
シングルトンパターンは使うと便利なデザインパターンでありますが、本当に必要なときにのみ使うべきです。
つまり,シングルトンパターンを使う理由が確実にある時のみに使うべきであるということです。
基本的にシングルトンパターンを使用しなくてはいけないという場面は滅多になさそうです。
ほとんど場合は設計上シングルトンパターンを使用しなくても実装可能であるため、設計をする際の引き出しの一つとして捉えるべきでしょう。