こちらの記事について社内外でちょっとした議論になったので、その内容をまとめてみました。
Singletonパターンを利用する理由は「外部からnewさせたくないから」だと思っていましたが、「そう書いた方がわかりやすいから」「staticではないメソッドも利用可能になるから」という理由の方が大きそうです。
【指摘】
・privateコンストラクタを持った時点でnewできない
newさせたくないだけなら、オブジェクト取得メソッドは不要。
Singletonパターンで無くても良い。
・Singletonパターンの思想の表現としてオブジェクト取得メソッドが必要
privateでオブジェクトを1つ生成、外部にはメソッド経由で提供する、とすれば、
「オブジェクトは1つのみ存在する」という思想をわかりやすく表現できる。
書籍ではSingletonパターンはオブジェクト取得メソッドが必要とされている。
流行っている書き方だから他の開発者にも理解しやすい、というのはありそう。
・Singletonパターンだとstaticではないメソッドも参照可能になる
Singletonパターンであればクラスへの参照ではなくオブジェクトへの参照となる。
そのため、staticではないメソッドの参照も可能となる。
具体的には、継承・オーバーライドができるというメリットがある。
【サンプルコード】
・StaticMemory.java
public class StaticMemory {
// 利用者側でnewさせたくないだけならこれだけで良い
private StaticMemory() {
}
private static int num = 0;
public static void setNum(int arg) {
num = arg;
}
public static int getNum() {
return num;
}
}
・SingletonMemory.java
public class SingletonMemory {
private static int num = 0;
private static SingletonMemory instance = new SingletonMemory();
protected SingletonMemory(){
}
public static SingletonMemory getInstance(){
return instance;
}
public static void setNum(int arg) {
num = arg;
}
public static int getNum() {
return num;
}
public int getNumCalc() {
return num * 2;
}
}
・SingletonMemoryChild.java
public class SingletonMemoryChild extends SingletonMemory {
private static SingletonMemoryChild instance = new SingletonMemoryChild();
private SingletonMemoryChild(){
}
public static SingletonMemoryChild getInstance(){
return instance;
}
// 下記はビルドエラー
// @Override
// public static int getNum() {
// return 100;
//}
@Override
public int getNumCalc() {
return super.getNumCalc() * 2;
}
}
・MemoryTestMain.java
public class MemoryTestMain {
// 要件:最新の値をメモリに保持したい
@SuppressWarnings("static-access")
public static void main(String[ ] args) {
int recentNum = 0; // 最新の値
// 普通のStaticなクラスを使った場合
recentNum = 1;
StaticMemory.setNum(recentNum);
System.out.println("Static1:"+StaticMemory.getNum());
// privateコンストラクタを定義すれば使う側でオブジェクトを作れない
// (下記はエラー)
// StaticMemory instance2 = new StaticMemory();
// instance2.setNum(recentNum);
// System.out.println("new1:"+instance2.getNum());
// オブジェクト取得メソッドを利用すれば非staticメソッドも参照可能
recentNum = 2;
SingletonMemory.getInstance().setNum(recentNum);
System.out.println
("Shingleton1:"+SingletonMemory.getInstance().getNum());
System.out.println
("Shingleton2:"+SingletonMemory.getInstance().getNumCalc());
// 直接参照だと非staticメソッドを参照できない
recentNum = 3;
SingletonMemory.setNum(recentNum);
System.out.println
("直接参照1:"+SingletonMemory.getNum());
// System.out.println
// ("直接参照2:"+SingletonMemory.getNumCalc());
// Singletoneなクラスを継承したクラスを利用
// 非staticメソッドはオーバーライドしたメソッドが呼び出される
recentNum = 4;
SingletonMemoryChild.getInstance().setNum(recentNum);
System.out.println
("Child1:"+SingletonMemoryChild.getInstance().getNum());
System.out.println
("Child2:"+SingletonMemoryChild.getInstance().getNumCalc());
}
}
【実行結果】
Staticクラス1:1
メソッドでget1:2
メソッドでget2:4
直接参照1:3
メソッドでget1(継承):4
メソッドでget2(継承):16