2017-10-16 66 views
-1

我正在寻找一种方法来实现一个抽象类(或有效抽象),只强制每个子类的一个实例。如何在Java中实现一个子类Singleton

我相当肯定这对使用Factory实现来说非常简单,但我很想知道是否可以在不知道所有子类类型的情况下完成,即通用单例实施程序类。

现在我大部分时间都只是想着这样的想法,所以我没有在寻找反馈来质疑这里的设计选择。

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

+3

不可能在没有私有构造函数的情况下强制实施Singleton模式,这使得外部子类化变得不可能。 – shmosel

+0

看看[问]和[帮助/话题]。这种开放式问题并不是一个很好的解决方案。 – pvg

+1

'提供证据表明这是不可能的',这不是如何工作......正如@pvg所说,这不是[help/on-topic],即使它是你的举证责任。 – Oleg

回答

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,你不会得到运行时的故障。

+0

我想出了类似于你的“选项2”的东西,但我认为它比我想要的更麻烦,而且我不认为对于以前没有使用过这种东西的人来说这是完全清楚的。所以我可能会诉诸不执行单身。 – jdodle

+0

我同意 - 选项2丑陋。恕我直言,强制单身人士应该是单身人士自己的工作,而不是它扩展的类别。 – Stormcloud

0

首先,一个通用的单例没有意义。
父类不应负责检索和管理其子类的实例。
它在两种方式(父母 - >孩子和孩子 - >父母)中建立了强大的联结。其次,正如shmosel所说的,子类化一个单独的(没有特殊的神器)是不可能的。
单例模式的关键是缺乏在单例类之外实例化类的能力,因此不需要提供公共构造函数。
在这些情况下,如何继承单例类?

要允许继承一个singleton类,您必须有一个公共构造函数,同时确保您没有多于一个该类的实例。 (例如Spring的控制容器的反转可能会这样做)(这是特殊工件的一个例子)。


作为一个方面说明,我不认为访问修饰符的package-private改性剂,可以让子类单身,但它的局限性是单身就单身只能在外面的包装调整等。

+0

我*想*他想写一组单个的基类。 – Stormcloud

+0

OP说:“我正在寻找一种实现抽象类(或有效抽象)的方法,只强制每个子类的一个实例。”所以我得出结论,抽象类处理子类的实例。 – davidxxx

+0

我同意。我不得不承认,我花了一些时间来思考这个基类的目的是什么。我仍然认为这是检查单身人士的错误类别。派生类中的私有构造函数将对此进行排序。 – Stormcloud

-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的做法,很容易实现,但breakes打开关闭原则来自SOLID

+0

这个工作的唯一原因是因为所有的类都是兄弟嵌套类,并且可以访问私有构造函数。不知道你为什么没有在代码片段中显示它。这打破了你提到的原则,远远超过枚举。你基本上实现了自己与使用枚举免费获得的相同的东西。 – Oleg

+0

@Oleg好吧,你可以将构造函数改为受保护的,它仍然可以作为单例基础 – mlecz