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

问题描述 投票:22回答:6

这是我的示例抽象单例类:

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来运行这些测试。

java inheritance singleton abstract-class classloader
6个回答
20
投票

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


8
投票

你试图让一个抽象类扮演两个截然不同的角色:

  • 可以具有多个可替换实现的(单例)服务的抽象工厂角色,
  • 服务接口角色,

最重要的是,你还希望服务是单例并在整个类系列上强制执行'singletoness',因为某些原因你不考虑缓存服务实例。

有人(我会)会说它闻起来非常糟糕,因为多种原因它违反了关注点,单身人士无法进行单元测试“等等。

其他人会说它没关系,它不需要很多不同的基础设施,并且在一些非常常见的第​​三方(传统)Java API中有一些流畅的界面。

不好的部分要求孩子们选择父工厂方法返回的实现。这个责任应该被推高并集中到抽象的超类中。否则,您将在非常不同的上下文中使用的模式混合在一起,抽象工厂(父级决定客户将要获得的类家族)和工厂方法(子工厂选择客户端将获得的内容)。

Factory Method实际上也不可能,因为您不能覆盖静态方法或构造函数。

有一些(丑陋的)方法来实现你的目标:

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

父级也可以使用反射,缓存实例等。

更加友好的测试和扩展解决方案只是标准的关注点分离。这些孩子本身不再是单身,但你将它们打包成一些内部包,你将其记录为“私有”,外部包中的公共抽象父将处理子实例的缓存或池化,强制执行任何实例化这些课程需要政策。


4
投票

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

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

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


4
投票

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

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


3
投票

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

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

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

这只是做事的坏方法。


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

© www.soinside.com 2019 - 2024. All rights reserved.