如何在Java中实现抽象的单例类?

时间:2010-03-17 00:01:53

标签: java inheritance singleton abstract-class classloader

以下是我的示例抽象单例类:

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 32bit上使用sun-java6-jre来运行这些测试。

6 个答案:

答案 0 :(得分:19)

摘要单身人士?对我来说听起来不太可行。 Singleton模式需要private构造函数,这已经使子类化成为不可能。你需要重新考虑你的设计。 Abstract Factory pattern可能更适合特定目的。

答案 1 :(得分:8)

从您的帖子开始,即使没有明确说明,听起来您希望抽象类扮演两个不同的角色。 角色是:

  • a的抽象工厂角色 (单身)服务可以有 多个可替代的 实现,
  • 服务 界面角色,

此外,您希望服务在整个类系列中单独执行'singletoness',由于某种原因,您不足以缓存服务实例。

这很好。

有人会说它闻起来非常糟糕,因为“违反了关注点的分离”,“单身人士和单位测试并不能很好地结合在一起”。

其他人会说这是好的,因为你赋予了在家庭中实例化正确孩子的责任,并且整体上也暴露了更流畅的界面,因为你不需要调解一个除暴露静态之外什么都不做的工厂方法

在您希望子项负责选择父工厂方法返回的实现之后,出现了什么问题。 这在设计方面是错误的,因为你委托给所有孩子,可以简单地将其推入并集中到抽象超类中,并且它还表明你正在混合使用在不同上下文中使用的模式,抽象工厂(父级决定什么类的类)客户将获得)和工厂方法(儿童工厂选择客户将获得的)。

工厂方法不仅不是必需的,而且也不可能使用工厂方法,因为它集中于实现或覆盖“实例”方法。静态方法和构造函数都没有覆盖。

回过头来看一个抽象单例的最初好或坏的想法,选择要暴露的行为,有几种方法可以解决最初的问题, 一个可能是以下,看起来很糟糕但我猜你接近你想要的东西:

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

父母也可以使用反射。

更多测试友好和扩展友好的解决方案只是让孩子不是单身,而是打包成一些内部包,你将其记录为“私人”和抽象的父,可以暴露“单身模仿”静态getInstance()和将缓存子实例,强制客户端始终获得相同的服务实例。

答案 2 :(得分:4)

A.getInstance()永远不会调用派生实例,因为它是静态绑定的。

我将对象的创建与实际对象本身分开,并创建一个返回特定类类型的适当factory。鉴于您的示例代码 - 是通过某个参数进行参数化还是类选择是静态的,您不清楚如何参数化?

你可能想重新考虑单身,顺便说一句。这是一个常见的反模式并使测试(特别是)变得很痛苦,因为测试中的类将提供他们自己的该类实例作为单例。您不能提供虚拟实现,也不能(轻松地)为每个测试创建新实例。

答案 3 :(得分:4)

单身人士有点yucky。摘要坚持继承,如果可能的话,你常常想要avoid。总的来说,我会重新考虑你要做的是simplest possible way,如果是的话,那么一定要使用工厂而不是单身人士(单身人士在unit tests而工厂很难替代可以告诉他们轻松替换测试实例。

一旦你开始考虑将它作为工厂实现,抽象的东西就会自行排序(要么显然是必要的,要么可以轻易地代替接口)。

答案 4 :(得分:3)

除了其他人指出的问题之外,在instance中使用A字段意味着您在整个VM中只能拥有一个单例。如果您还有:

public class C extends A {
    private C() { }
    static {
        instance = new C();
    }
    //...implementations of my abstract methods...
}

...然后,最后加载BC中的任何一个都将获胜,而另一个的单例实例将会丢失。

这只是做事的坏方法。

答案 5 :(得分:1)

我发现了在抽象类中使用Singleton的更好方法,该类使用静态Map维护子类的实例。

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);
        }
    }
}

发件人:https://www.cnblogs.com/wang9192/p/3975748.html