これがサンプルのシングルトンクラスのサンプルです。
public abstract class A {
protected static A instance;
public static A getInstance() {
return instance;
}
//...rest of my abstract methods...
}
そしてここに具体的な実装があります:
public class B extends A {
private B() { }
static {
instance = new B();
}
//...implementations of my abstract methods...
}
残念ながら、クラスBの静的コードを実行できないため、インスタンス変数が設定されることはありません。私はこれを試しました:
Class c = B.class;
A.getInstance() - returns null;
この
ClassLoader.getSystemClassLoader().loadClass("B");
A.getInstance() - return null;
これらの両方をEclipseデバッガーで実行すると、静的コードは実行されません。静的コードを実行するための唯一の方法は、Bのコンストラクターのアクセシビリティーをpublicに変更して呼び出すことです。
これらのテストを実行するために、Ubuntu 32ビットでSun-Java6-jreを使用しています。
シングルトンを抽象化しますか?私には実行可能に聞こえません。シングルトンパターンにはprivate
コンストラクターが必要で、これによりサブクラス化はすでに不可能になっています。デザインを再考する必要があります。 Abstract Factoryパターン は、特定の目的に適しています。
あなたは抽象クラスに2つの非常に異なる役割を果たそうとしている:
さらに、サービスをシングルトンにして、クラスのファミリー全体に「singletoness」を適用することも必要です。何らかの理由で、サービスインスタンスのキャッシュを検討していません。
誰か(私はそう思います)は、それが懸念の分離に違反する複数の理由で、シングルトンがユニットテストを不可能にするなどの理由で、それは非常に悪臭を放つと言います。
他の誰かがそれは大丈夫だと言うでしょう、それは多くの異なるインフラストラクチャを必要とせず、いくつかの非常に一般的なサードパーティ(レガシー)で見られるような流暢なインターフェイスを持っていますJava API 。
悪い点は、親ファクトリメソッドが返す実装を選択するように子供に要求することです。その責任を押し上げて、抽象スーパークラスに集中化する必要があります。それ以外の場合は、非常に異なるコンテキストで使用されるパターン、Abstract Factory(親がクライアントが取得するクラスのファミリーを決定)とFactoryメソッド(子ファクトリがクライアントが取得するものを選択)を混合します。
静的メソッドやコンストラクターをオーバーライドできないため、ファクトリメソッドも実際には不可能です。
ただし、目的を達成するためのいくつかの(醜い)方法があります。
public abstract class A{
public static A getInstance(...){
if (...)
return B.getInstance();
return C.getInstance();
}
public abstract void doSomething();
public abstract void doSomethingElse();
}
public class B extends A{
private static B instance=new B();
private B(){
}
public static B getInstance(){
return instance;
}
public void doSomething(){
...
}
...
}
//do similarly for class C
親はリフレクション、キャッシュインスタンスなどを使用することもできます。
よりテストと拡張に適したソリューションは、懸念事項の標準的な分離です。子自体はもはやシングルトンになることはありませんが、それらを「プライベート」として文書化する内部パッケージにパッケージ化し、外部パッケージのパブリック抽象親が子インスタンスのキャッシュまたはプールを処理して、インスタンス化を強制しますこれらのクラスにはポリシーが必要です。
A.getInstance()
は静的にバインドされているため、派生インスタンスを呼び出すことはありません。
オブジェクトの作成を実際のオブジェクト自体から分離し、適切な factory を作成して特定のクラスタイプを返します。例のコードを考えると、それをどのようにパラメーター化するかは明確ではありません-いくつかの引数を介してパラメーター化されているか、それともクラス選択は静的ですか?
さて、シングルトンを再考したいと思うかもしれません。テスト中のクラスはそのクラスの独自のインスタンスをシングルトンとして提供するため、これは一般的なアンチパターンであり、テスト(特に)を困難にします。ダミーの実装を提供したり、(簡単に)テストごとに新しいインスタンスを作成したりすることはできません。
他の人が指摘した問題に加えて、instance
にA
フィールドがあることは、VM全体でoneシングルトンしか持てないことを意味します。あなたも持っている場合:
public class C extends A {
private C() { }
static {
instance = new C();
}
//...implementations of my abstract methods...
}
...次に、最後に読み込まれたB
またはC
のどちらかが勝ち、もう一方のシングルトンインスタンスは失われます。
これは物事を行うのにちょうど悪い方法です。
サブクラスのインスタンスを維持するために静的マップを使用する抽象クラスでシングルトンを使用するより良い方法を見つけました。
public abstract class AbstractSingleton {
private static Map<String, AbstractSingleton> registryMap = new HashMap<String, AbstractSingleton>();
AbstractSingleton() throws SingletonException {
String clazzName = this.getClass().getName();
if (registryMap.containsKey(clazzName)) {
throw new SingletonException("Cannot construct instance for class " + clazzName + ", since an instance already exists!");
} else {
synchronized (registryMap) {
if (registryMap.containsKey(clazzName)) {
throw new SingletonException("Cannot construct instance for class " + clazzName + ", since an instance already exists!");
} else {
registryMap.put(clazzName, this);
}
}
}
}
@SuppressWarnings("unchecked")
public static <T extends AbstractSingleton> T getInstance(final Class<T> clazz) throws InstantiationException, IllegalAccessException {
String clazzName = clazz.getName();
if (!registryMap.containsKey(clazzName)) {
synchronized (registryMap) {
if (!registryMap.containsKey(clazzName)) {
T instance = clazz.newInstance();
return instance;
}
}
}
return (T) registryMap.get(clazzName);
}
public static AbstractSingleton getInstance(final String clazzName)
throws ClassNotFoundException, InstantiationException, IllegalAccessException {
if (!registryMap.containsKey(clazzName)) {
Class<? extends AbstractSingleton> clazz = Class.forName(clazzName).asSubclass(AbstractSingleton.class);
synchronized (registryMap) {
if (!registryMap.containsKey(clazzName)) {
AbstractSingleton instance = clazz.newInstance();
return instance;
}
}
}
return registryMap.get(clazzName);
}
@SuppressWarnings("unchecked")
public static <T extends AbstractSingleton> T getInstance(final Class<T> clazz, Class<?>[] parameterTypes, Object[] initargs)
throws SecurityException, NoSuchMethodException, IllegalArgumentException,
InvocationTargetException, InstantiationException, IllegalAccessException {
String clazzName = clazz.getName();
if (!registryMap.containsKey(clazzName)) {
synchronized (registryMap) {
if (!registryMap.containsKey(clazzName)) {
Constructor<T> constructor = clazz.getConstructor(parameterTypes);
T instance = constructor.newInstance(initargs);
return instance;
}
}
}
return (T) registryMap.get(clazzName);
}
static class SingletonException extends Exception {
private static final long serialVersionUID = -8633183690442262445L;
private SingletonException(String message) {
super(message);
}
}
}