如何在Java中实现可子类化的Singleton

时间:2017-10-16 19:14:30

标签: java design-patterns singleton

我正在寻找一种方法来实现一个抽象类(或有效抽象),它只强制执行每个子类的一个实例。

我很确定使用Factory实现它会非常简单,但我很想知道是否可以在不知道所有子类类型的情况下完成,即通用的单例执行器类。

现在我大多只是想要这样的想法,所以我不是在寻找对这里的设计选择提出疑问的反馈。

我正在使用的语言是Java,但是现在我不一定担心实现细节,除非在Java中不可能,否则当然会提供证据证明它不可能。

3 个答案:

答案 0 :(得分:0)

我想知道你想要做什么。我想到了几种可能性,并且知道它的发展方向可能有所帮助。

选项1

因此,您可以尝试使用enum类型作为抽象基类。然后,每个枚举常量由语言保证为单例。枚举可以有常量实现的抽象方法。如果您有很多实现常量和许多抽象方法要实现,那么这将起作用,但编译单元变得非常大并且难以导航。如果它开始失控,你当然可以将一些工作委托给辅助类。

选项2

你可以做的是让基类构造函数检查它的实际类型并将其存储在静态HashSet(或类似的)中。如果条目已存在,那么您有两个相同单例的实例。像

这样的东西
public abstract class BaseClass {
    private static HashSet<Class<?>> instances = new HashSet<>();

    protected BaseClass() {
        checkInstances();
    }

    private synchronized void checkInstances() {
        boolean duplicate = instances.add(getClass());

        if (duplicate) {
           throw new RuntimeException("Duplicate class " + getClass().getName()); 
        }
    }
}

这样做的缺点是错误发生在运行时并且代码不是特别漂亮,你可以看到你可能需要考虑集合的同步

选项3

您的最终选择是不要求基类强制执行此限制。派生类的工作可能应该决定它们是否是单身人士。派生类中的私有构造函数是最简单的方法。

<强>结论

我个人会实现选项1或选项3,因为您不会遇到运行时故障。

答案 1 :(得分:0)

首先,一般的单身人士没有意义 父类不应负责检索和管理其子类的实例 它以两种方式创建强耦合(parent-&gt; child和child-&gt; parent)。

其次,正如shmosel所说,不可能对单例进行子类化(没有特殊的工件) 单例模式的关键是缺乏在单例类之外实例化类的能力,因此不必提供公共构造函数。
在这些条件下,如何对单例类进行子类化?

要允许对单例类进行子类化,您必须拥有公共构造函数,同时确保您只有一个类的实例。
诸如Spring之类的控制容器的反转可能会这样做(这是一个特殊工件的例子)。

作为旁注,我不考虑访问修饰符调整,例如package-private修饰符,它可以允许对单例进行子类化,但是它的限制是单例只是在单例之外的单例封装

答案 2 :(得分:-1)

我想说,单身人士很糟糕。但发现这个问题很有趣,所以我创造了你想要的东西。 这是代码

  public static abstract class SingletonBase {
        private static HashSet<SingletonBase> instances = new HashSet<>();

        {
            for (SingletonBase sb : instances) {
                if (sb.getClass() == this.getClass()) throw new RuntimeException("there is already 1 instance");
            }
        }

        public static <E> E getInstance(Class<E> clazz) {
            if (!SingletonBase.class.isAssignableFrom(clazz)) {
                throw new RuntimeException();
            }
            for (SingletonBase sb : instances) {
                if (sb.getClass() == clazz) return (E) sb;
            }

            try {
                return clazz.newInstance();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return null;

        }

        private SingletonBase() {
            instances.add(this);
        }

    }
    static class SingletonTest extends SingletonBase{

    }

    static class SecondSingletonTest extends SingletonBase{

    }

    public static void main(String[] args) {
        for(int i=0;i<=10;i++)
            System.out.println(  SingletonBase.getInstance(SingletonTest.class));
        for(int i=0;i<=10;i++)
            System.out.println(  SingletonBase.getInstance(SecondSingletonTest.class));
        //throws exception, because we try to create second instance here
        new SingletonTest();
    }

创建泛型类的方法存在一些问题,这些问题在此解决: 首先,您不能创建多个实例,因此基类必须跟踪所有实例,当您尝试使用new创建另一个实例时,它将抛出异常。其次,您需要获取特定类的实例。如果您不想创建这样的实例:

SingletonBase.getInstance(SecondSingletonTest.class)

您可以像这样创建子类:

 static class SingletonTest extends SingletonBase{
        public static  SingletonTest getInstance(){
            return getInstance(SingletonTest.class);
        }
    }

还有人建议使用ENUM方法,它很容易实现,但是从SOLID打破封闭原则