「Singleton」ってどんなパターン?
あるクラスのオブジェクトを一つだけ作り、各クラスで共有したい場合に使うパターンです。
メリット
「システムの起動から、終了まで、そのクラスのオブジェクトが一つしか存在しないこと」を保証できます。
例えば、マルチスレッドのプログラムだと、どちらのスレッドが唯一のインスタンスを持つスレッドなのか判定させるのが難しかったりしますしね。
使用例
- JavaのRuntimeクラス等は、JVM上で一つのオブジェクトしか存在してはいけない。
- アプリケーション全体を監視するタイマークラス
具体的に実装してみる。
SingletonTest.java(シングルトンで実装したクラス)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package Singleton; /** * Singletonのテストクラスです。 */ public class SingletonTest { //インスタンスをクラス変数に格納して、グローバルな存在にする。 static SingletonTest onlyInstance; //privateにすることで、不用意にnewされることを防ぐ。 private SingletonTest() { //何もしない。 } static SingletonTest getInstance() { if(onlyInstance == null) { onlyInstance = new SingletonTest(); } return onlyInstance; } } |
getInstanceで呼び出し元に、インスタンスを返していますが、その際の変数はクラス変数(static変数)にして、システムで共通の変数としています。
自分自身のインスタンスをグローバルなstaticメンバとして持ちます。
コンストラクタをprivateにすることで、不用意にnewされることを防いでいます。
コンストラクタがprivateなので、自分自身のクラスからしかnewができません。(なので、getInstanceというstaticメソッドを作ってその中で自分自身をnewするように実装します。)
Main.java(呼び出すクラス)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package Singleton; public class Main { public static void main(String[] args) { //1回目の呼び出し。 SingletonTest test1 = SingletonTest.getInstance(); //2回目の呼び出し。 SingletonTest test2 = SingletonTest.getInstance(); System.out.println(test1); System.out.println(test2); } } |
実行結果
2回のインスタンス生成メソッド(getInstance)の、呼び出しを行っているのにも関わらず、下記のように同じ「クラス名@英数字」になっていることから、同じインスタンスが生成されていることがわかります。
注意点1:複数スレッド時の問題
複数スレッドで同時にプログラムが実行されるときに2回newされる可能性がある点です。
対策
1度目に実行しようとした際にメソッドをロックしてしまうことです。
Javaなら
synchronizedキーワードを使う。
C#など
ロックオブジェクトを使う
ロックオブジェクトとlock構文を使います。マルチスレッドの場合に非同期で実行しようとするプログラムを同期化してくれます。
staticクラスを使う(C#2.0より)
こうするだけでstaticのクラスを実装できます。なおその場合メンバ変数やメソッドも全てstaticにする必要があります。
getInstanceだったり、lockだったりと余計な構文を覚える必要がないので非常に楽です。新しくプログラムを作成するときは積極的に活用していくと良いでしょう。
その場合のシングルトンの判断方法としては「コンストラクタがprivateになっているか」で判断すると良いでしょう。
staticクラスの注意点
継承が書けないことです。なので拡張性はないクラスになるのでそこだけは注意しましょう。その場合だけは普通のシングルトン実装をするようにしましょう。
(ただ、基本的にシングルトンのクラスを継承で作りたいと思ったことはないので基本的には全てstaticクラスで実装すると良いでしょう。)
JavaScriptなら
以下のように記述するだけでシングルトンになります。
1 |
export default new クラス名(); |
その他方法
初期化子を使う方法もあります。
注意点2: テストコード時の問題
テストコード1を実行したときにシングルトンだと同じインスタンスを使い回すので連続で実行すると違う結果になる場合があります。
対策
- テストの間にインスタンスを初期化する処理を実行する。baseクラスとして継承させても良いかもです。
この記事へのコメントはありません。