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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2459061/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 08:00:39  来源:igfitidea点击:

How can I implement an abstract singleton class in Java?

javainheritancesingletonabstract-classclassloader

提问by Simon

Here is my sample abstract singleton class:

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

public abstract class A {
    protected static A instance;
    public static A getInstance() {
        return instance;
    }
    //...rest of my abstract methods...
}

And here is the concrete implementation:

这是具体的实现:

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

Unfortunately I can't get the static code in class B to execute, so the instance variable never gets set. I have tried this:

不幸的是,我无法执行 B 类中的静态代码,因此永远不会设置实例变量。我试过这个:

Class c = B.class;
A.getInstance() - returns null;

and this

和这个

ClassLoader.getSystemClassLoader().loadClass("B");
A.getInstance() - return null;

Running both these in the eclipse debugger the static code never gets executed. The only way I could find to get the static code executed is to change the accessibility on B's constructor to public, and to call it.

在 Eclipse 调试器中运行这两个静态代码永远不会被执行。我能找到执行静态代码的唯一方法是将 B 的构造函数的可访问性更改为 public,然后调用它。

I'm using sun-java6-jre on Ubuntu 32bit to run these tests.

我在 32 位 Ubuntu 上使用 sun-java6-jre 来运行这些测试。

回答by BalusC

Abstract Singleton? Doesn't sound viable to me. The Singleton pattern requires a privateconstructor and this already makes subclassing impossible. You'll need to rethink your design. The Abstract Factory patternmay be more suitable for the particular purpose.

抽象单例?对我来说听起来不可行。单例模式需要一个private构造函数,这已经使得子类化变得不可能。你需要重新考虑你的设计。该抽象工厂模式可能更适合于特定的目的。

回答by Brian Agnew

A.getInstance()will never call a derived instance since it's statically bound.

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

I would separate the creation of the object from the actual object itself and create an appropriate factoryreturning a particular class type. It's not clear how you'd parameterise that, given your example code - is it parameterised via some argument, or is the class selection static ?

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

You may want to rethink the singleton, btw. It's a common antipatternand makes testing (in particular) a pain, since classes under test will provide their own instance of that class as a singleton. You can't provide a dummy implementation nor (easily) create a new instance for each test.

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

回答by Bill K

Singletons are kind of yucky. Abstract insists on inheritance which you more often than not want to avoidif possible. Overall I'd rethink if what you are trying to do is the simplest possible way, and if so, then be sure to use a factory and not a singleton (singletons are notoriously hard to substitute in unit testswhereas factories can be told to substitute test instances easily).

单身人士有点恶心。Abstract 坚持继承,如果可能的话,你往往不想避免。总的来说,我会重新考虑你想要做的是否是最简单的方法,如果是这样,那么一定要使用工厂而不是单例(众所周知,单例很难在单元测试中替代,而工厂可以被告知替代)测试实例很容易)。

Once you start looking into implementing it as a factory the abstract thing will sort itself out (either it will clearly be necessary or it may factor out easily in place of an interface).

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

回答by Matt McHenry

In addition to problems others have pointed out, having the instancefield in Ameans that you can only have onesingleton in the entire VM. If you also have:

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

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

... then whichever of Bor Cgets loaded last will win, and the other's singleton instance will be lost.

... 那么最后加载的BC将获胜,而另一个的单例实例将丢失。

This is just a bad way to do things.

这只是一种糟糕的做事方式。

回答by Benedetto Fiorelli

You are trying to get an abstract class play two very different roles:

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

  • the abstract factory role for a (singleton) service that can have multiple substitutable implementations,
  • the service interface role,
  • 可以有多个可替代实现的(单例)服务的抽象工厂角色,
  • 服务接口角色,

and on top of that you also want the service to be singleton and enforce 'singletoness' on the entire family of classes, for some reason you aren't considering caching the service instances.

最重要的是,您还希望服务是单例的,并在整个类家族上强制执行“单一性”,出于某种原因,您不考虑缓存服务实例。

Somebody (I would) will say it smells very bad, for multiple reasons it violates separation of concerns, singletons make unit testing impossible", etc.

有人(我会)会说它闻起来很糟糕,出于多种原因,它违反了关注点分离,单例使单元测试变得不可能”等。

Somebody else will say it's ok-ish, it doesn't need a lot of different infrastructure and has kind of fluent-ish interface that you see in some very common third party (legacy) Java API.

其他人会说它没问题,它不需要很多不同的基础设施,并且具有您在一些非常常见的第​​三方(遗留)Java API 中看到的流畅的界面。

The bad part is demanding the children to select what implementation should the parent factory method return. That responsibility should be pushed up and centralised into the abstract superclass. Otherwise you are mixing together patterns that are used in very different contexts, Abstract Factory (parent decide what family of classes clients are going to get) and Factory Method (children factories select what the clients will get).

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

Factory Method is also not practically possible because you can't override static methods, nor constructors.

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

There are some (ugly) ways to achieve your objective though:

不过,有一些(丑陋的)方法可以实现您的目标:

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

The parent could also use reflection, cache instances, etc.

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

A more test and extension friendly solution is simply standard separation of concerns. The children aren't going to be singleton anymore per se, but you package them into some internal package that you will document as "private" and a public abstract parent in an external package will handle caching or pooling of children instances, enforcing whatever instantiation policy is required on these classes.

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

回答by Zhou Hongbo

I found a better way to use Singleton in abstract class, which use a static Map to maintain the instance of subclass.

我找到了一种在抽象类中使用 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);
        }
    }
}

From: https://www.cnblogs.com/wang9192/p/3975748.html

来自:https: //www.cnblogs.com/wang9192/p/3975748.html