デザインパターン Singletonについて勉強した

Iteratorパターンに引き続き、Singletonパターンについて理解したことの再確認になります。
chiopino.hatenablog.com

使用言語はJavaです。

Singletonパターンとは

通常インスタンスを複数作成するとそれぞれのインスタンスが別々の参照先を持ち、別々の値を持っています。
つまり、下記のインスタンスは同一性比較(==比較)では不一致となります。

File a = new File("aaaa");
File b = new File("aaaa");
System.out.println(a==b);    //false

インスタンスとはそういうものですが、時には同じクラスのインスタンスが複数あっては困るような場合もあります。
そのような時に使用するのがSingletonパターンです。

Singletonパターンの例

Singletonパターン自体は1つのクラスで構成できます。

public class Person {

	private static Person singleton = new Person();

	private Person() {
	}

	public static Person getInstance() {
		return singleton;
	}
}

コンストラクタを呼ばれると、呼ばれた分だけインスタンスが生成されてしまいます。
そこで外部からの呼び出しを禁止するためにprivateを付与します。

ではどうやって1つ目のインスタンスを生成するかというと、singletonフィールドの初期化でコンストラクタを呼んでいます。
外部からのコンストラクタ呼び出しを禁止し、クラスフィールドで一つだけインスタンスを生成することで、このアプリケーションでは起動から終了まで、このインスタンスしか使用できないことになります。

getInstanceメソッドを呼ぶことでsingletonフィールドを返します。

singletonフィールドをprivateでなくしてやれば、publicにしてやればgetInstanceメソッドは不要になるのではないかと思いますが、そうはいきません。
privateにしてしまうと、外部からの代入も可能になってしまい、nullで上書きされてしまう可能性があるからです。

使用例

public class Main {

	public Main() {
		Person a_san = Person.getInstance();
		Person b_san = Person.getInstance();
		Person c_san = a_san.getInstance();
		Person d_san = new Person();
		Person e_san = (Person)a_san.clone();

		System.out.println("aさん = bさん" + (a_san == b_san ? "です" : "ではありません"));
		System.out.println("aさん = cさん" + (a_san == c_san ? "です" : "ではありません"));
		System.out.println("aさん = dさん" + (a_san == d_san ? "です" : "ではありません"));
		System.out.println("aさん = eさん" + (a_san == e_san ? "です" : "ではありません"));

	}

	public static void main(String[] args) {
		new Main();
	}
}

a_sanとb_sanはクラスメソッドであるgetInstanceメソッドによって初期化します。

c_sanはa_sanのインスタンスからgetInstanceを呼び出しています。
間違いではありませんが、インスタンスからクラスメソッドを呼び出しているということで警告が出ているかと思います。

d_sanはPersonクラスのコンストラクタを呼び出そうとしていますが、コンストラクタはprivateにしているので、コンパイルエラーが発生します。
コンストラクタを呼べないので、複数のインスタンスを生成されることを防止できています。

e_sanはa_sanのインスタンスからcloneメソッドを呼び出そうとしています。
cloneメソッドの詳細については説明を省きますが、簡単に言えばあるインスタンスと同じ値を持った別のインスタンスを複製する手段です。
cloneメソッドを使用するにはCloneableインタフェースを実装するなど準備が必要ですが、Personクラスはそのような準備をしていないので、こちらもコンパイルエラーが発生します。


複数のインスタンスを生成する手段がないことが確認できたところで、コンパイルできるようにコードを修正します。

public class Main {

	public Main() {
		Person a_san = Person.getInstance();
		Person b_san = Person.getInstance();
		Person c_san = a_san.getInstance();
		Person d_san = null;//new Person();           //修正
		Person e_san = null;//(Person)a_san.clone();  //修正

		System.out.println("aさん = bさん" + (a_san == b_san ? "です" : "ではありません"));
		System.out.println("aさん = cさん" + (a_san == c_san ? "です" : "ではありません"));
		System.out.println("aさん = dさん" + (a_san == d_san ? "です" : "ではありません"));
		System.out.println("aさん = eさん" + (a_san == e_san ? "です" : "ではありません"));

	}

	public static void main(String[] args) {
		new Main();
	}
}

手っ取り早くd_sanとe_sanをnullにしてしまいました。
c_sanは警告は出ていますが、コンパイルは可能なので残すことにします。

では実行してみます。

aさん = bさんです
aさん = cさんです
aさん = dさんではありません
aさん = eさんではありません

aさんとbさん、それにcさんは同一のインスタンスであることがわかります。
またdさんとeさんはnullを代入してしまったので不一致になっています。

まとめ

Singletonパターンはあるクラスのインスタンスを一つしか生成できないように制限を課す方法です。
もちろん最初からnew Person()を一度しか呼ばないように注意することもできますが、うっかり二度呼んでしまってもそれに気づくのはおそらくバグが起こってからでしょう。
もしかしたら1年後、忘れた頃になんてことも考えられます。
そんなことに神経をすり減らすくらいならコンパイラにチェックさせる方が楽なものです。