| ホーム > 技術情報 > Singletonのサブクラス化 | 検索 | 更新情報 |
| Javaの入門書 | デザパタ本 | 著書 |
Javaで、Singletonのサブクラス化のコーディングを試みます。インスタンスの唯一性をどのように保証するかを考えましょう。
Singletonパターンのサブクラス化に関連したJavaのサンプルコードを紹介します。
このページを作成するにあたっては、GoF本はもちろんのこと、 デザインパターン・メーリングリストでのメールのやりとりを参考にしました。 澤田聡司さん、小山さん、中野靖治さん、宮本さん、杉村貴士さん、出井秀行さん、 その他のみなさんに感謝します。 特に、澤田さんから多くの情報と示唆をいただきました。感謝します。
(メモ:GoF本のSingletonのバリエーションに言及する必要あり。いつか加筆すること)
メモ:以下のソースではRuntimeExceptionを使用しています。 一般に、RuntimeExceptionの利用には注意が必要ですが、 このソースでRuntimeExceptionをthrowしている部分は「プログラマの誤り」に起因するところであり、 正しい用法のはずです。
JavaでSingletonを作るときの典型的な方法は次の通りです。
例えば、次のようにします。 以下ではDisplayクラスをSingletonとして実現しています。 Displayクラスのインスタンスは唯一で、 Display.getInstance()という式で得ることができます。
class Display {
private static Display _instance = new Display();
private Display() {
}
public static Display getInstance() {
return _instance;
}
public void display(String msg) {
System.out.println("Display: " + msg);
}
}
class Main {
public static void main(String[] args) {
Display obj1 = Display.getInstance();
Display obj2 = Display.getInstance();
if (obj1 == obj2) {
System.out.println("obj1 == obj2");
}
obj1.display("Hello");
}
}
実行結果は次の通りです。
obj1 == obj2 Display: Hello
ただし、上のようなコーディングだと、Displayのサブクラスを作れないという制約が生じます。 コンストラクタをprivateにしているため、 サブクラスを作ると、コンパイル時のエラーになってしまうからです。 かといってコンストラクタをpublicやprotectedにすると、 プログラマが誤ってnewする危険性が生じてしまいます。
次の節では、この制約を解消します。
前節の制約を解消する方法を考えてみましょう。
import java.util.HashMap;
class Display {
private static HashMap _classnameToInstance = new HashMap();
private static Object _lock = new Object();
protected Display() {
synchronized (_lock) {
String classname = this.getClass().getName();
if (_classnameToInstance.get(classname) != null) {
throw new RuntimeException("Already created: " + classname);
}
_classnameToInstance.put(classname, this);
}
}
public static Display getInstance(String classname) {
synchronized (_lock) {
Display obj = (Display)_classnameToInstance.get(classname);
if (obj == null) {
try {
Class cls = Class.forName(classname);
obj = (Display)cls.newInstance();
} catch (ClassNotFoundException e) {
throw new RuntimeException(classname + " is not found");
} catch (IllegalAccessException e) {
throw new RuntimeException(classname + " cannot be accessed.");
} catch (InstantiationException e) {
throw new RuntimeException(classname + " cannot be instantiated.");
}
}
return obj;
}
}
public void display(String msg) {
System.out.println("Display: " + msg);
}
}
class ScreenDisplay extends Display {
public void display(String msg) {
System.out.println("ScreenDisplay: " + msg);
}
}
class HyperDisplay extends Display {
public void display(String msg) {
System.out.println("HyperDisplay: " + msg);
}
}
class Main {
public static void main(String[] args) {
Display screen1 = Display.getInstance("ScreenDisplay");
Display screen2 = Display.getInstance("ScreenDisplay");
Display hyper1 = Display.getInstance("HyperDisplay");
Display hyper2 = Display.getInstance("HyperDisplay");
screen1.display("Hello");
screen2.display("Hello");
hyper1.display("Hello");
hyper2.display("Hello");
if (screen1 == screen2) {
System.out.println("screen1 == screen2");
}
if (hyper1 == hyper2) {
System.out.println("hyper1 == hyper2");
}
}
}
実行結果は次の通りです。
ScreenDisplay: Hello ScreenDisplay: Hello HyperDisplay: Hello HyperDisplay: Hello screen1 == screen2 hyper1 == hyper2
上のコーディングだと、 Displayクラス・ScreenDisplayクラス・HyperDisplayクラスのそれぞれにインスタンスが1個ずつ存在することになります (正確には0個の場合もありえます。例えば上記のMainクラスを実行したとき、Displayのインスタンスは0個です)。
次の節では、 「クラスごとにインスタンスを1個」確保するのではなく、 「クラス階層全体でインスタンスを1個」する方法を示します。
import java.util.HashMap;
class Display {
private static Display _instance = null;
private static Object _lock = new Object();
protected Display() {
synchronized (_lock) {
if (_instance != null) {
throw new RuntimeException("Already created: " + _instance.getClass().getName());
}
_instance = this;
}
}
public static Display getInstance(String classname) throws ClassNotFoundException {
synchronized (_lock) {
Class cls = Class.forName(classname);
if (_instance == null) {
try {
Display obj = (Display)cls.newInstance();
// assert obj == _instance;
} catch (IllegalAccessException e) {
throw new RuntimeException(classname + " cannot be accessed.");
} catch (InstantiationException e) {
throw new RuntimeException(classname + " cannot be instantiated.");
}
} else if (!_instance.getClass().isAssignableFrom(cls)) {
throw new ClassCastException(classname);
}
return _instance;
}
}
public void display(String msg) {
System.out.println("Display: " + msg);
}
}
class ScreenDisplay extends Display {
public void display(String msg) {
System.out.println("ScreenDisplay: " + msg);
}
}
class HyperDisplay extends Display {
public void display(String msg) {
System.out.println("HyperDisplay: " + msg);
}
}
class Main {
public static void main(String[] args) throws Exception {
Display screen1 = Display.getInstance("ScreenDisplay");
Display screen2 = Display.getInstance("ScreenDisplay");
screen1.display("Hello");
screen2.display("Hello");
if (screen1 == screen2) {
System.out.println("screen1 == screen2");
}
try {
Display hyper = Display.getInstance("HyperDisplay");
hyper.display("Hello");
} catch (ClassCastException e) {
System.out.println("ClassCastException (OK)");
}
}
}
実行結果は次の通りです。
ScreenDisplay: Hello ScreenDisplay: Hello screen1 == screen2 ClassCastException (OK)
他にもバリエーションがありますけれど、まずはここまで。
あなたのご意見・感想をお送りください。 あなたの一言が大きなはげみとなりますので、どんなことでもどうぞ。