难以理解为何将单例子类化与单例的定义相矛盾?

时间:2019-06-22 03:35:21

标签: java design-patterns singleton

从这里的其他answers来看,我发现本质上将单例子类化与拥有单例的含义相矛盾。但是,我不太明白为什么getInstance返回超类的相同共享实例时会违反此属性。例如:

class Singleton {
    private static Singleton instance = null;
    protected Singleton() {

    }
    public static Singleton getInstance(){
        if (instance == null) {
            instance = new Singleton();         
        } 
        return instance;
    }
}

class SubclassSingleton extends Singleton {
    public SubclassSingleton() {

    }
}


SubclassSingleton x =  new SubclassSingleton();
SubclassSingleton y = new SubclassSingleton();
System.out.println(x.getInstance() == y.getInstance()); // true

是的,从技术上讲xySingleton的实例,但是获取它们的实例仍然会导致共享资源,并且在构造{{1 }}基本上没有任何作用-静态方法x将返回父类的共享结构。因此,我很难理解,尽管被子类化了,但是这本质上违反了拥有getInstance()一个共享实例的行为。

3 个答案:

答案 0 :(得分:4)

tl; dr :虽然我认为可以以可以将其子类化的方式设计单例,但是在设计类时必须格外小心。在大多数情况下,我认为这样做是不值得的。


我不会说子类化是一种固有的违反单例原则的方法,但是在设计此类时必须格外小心。要了解原因,让我们看看这个问题中的相反定义。

第一个是定义Singleton Pattern。本质上说,对于该类,该类最多会有一个实例,并且所有类都使用同一实例。在Java中,这通常意味着无法重新初始化单例(即,必须避免再次调用new ...)。为什么?因为您程序中的某个地方,某些部分可能包含对“旧”单例实例的引用x。当您重新初始化单例时,x仍将引用旧的单例,而将来对单例访问器的所有调用将保留对新单例的引用。您有两个相同类的实例,因此您违反了单例。

与单例wrt相反的定义。子类是Liskov Substitution Laws。它从本质上说,类X的每个子类都必须在X处可用,并且程序仍然可以工作。这就是为什么SquareRectangle永远不应该成为继承关系的原因(一个只需要定义一个属性[length],而另一个则需要两个[{{1}] }&width]。

这意味着,对于单例而言,所有子类实例也都算作超类的实例。您不仅必须控制所讨论类的实例化,而且还必须控制其所有(!)子类,甚至是您不受控制的子类。在最坏的情况下,您甚至都不知道子类的所有子类。

我编写了一个小例子,涵盖了我所看到的情况。请注意,这个示例并不能保证是完美的,它只是说明了使height可以正确地子化所必须进行的扩展。

Singleton

如果对class Singleton { private static volatile Singleton INSTANCE = null; private static Supplier<? extends Singleton> factory; public static void setSingletonFactory(Supplier<? extends Singleton> factory) { Singleton.factory = factory; } public static Singleton getInstance() { if (INSTANCE == null) { synchronized (Singleton.class) { if (INSTANCE == null) { INSTANCE = factory.get(); } } } return INSTANCE; } protected Singleton() { if (INSTANCE == null) { synchronized (Singleton.class) { if (INSTANCE == null) { // Set attribute of Singleton as necessary } else { throw cannotReinitializeSingletonIllegalStateException(); } } } else { throw cannotReinitializeSingletonIllegalStateException(); } } private static IllegalStateException cannotReinitializeSingletonIllegalStateException() { return new IllegalStateException("Cannot reinitialize Singleton"); } public static void main(String... args) { Singleton.setSingletonFactory(SubSingleton.SUB_SINGLETON_FACTORY); Singleton instanceOne = Singleton.getInstance(); Singleton instanceTwo = Singleton.getInstance(); System.out.println(instanceOne == instanceTwo); try { SubSingleton.SUB_SINGLETON_FACTORY.get(); } catch (IllegalStateException e) { System.out.println("Rightfully thrown IllegalStateException:"); e.printStackTrace(System.out); } } } class SubSingleton extends Singleton { public static final Supplier<SubSingleton> SUB_SINGLETON_FACTORY = SubSingleton::new; private SubSingleton() {} } 的调用对性能至关重要,则必须对两个实例(getInstance()和构造函数)进行双重检查锁定,这对您来说是至关重要的,也就是说,您无力锁定每次调用。此外,如果按预期使用单例,则永远不要抛出对getInstance()的调用。在构造函数中,我们需要仔细检查锁定以强制执行singleton属性。在getInstance()中,我们需要它来防止过早抛出getInstance()

看到上面的概念证明,很明显必须小心。仍然有可能在程序的某个地方,在执行对IllegalStateException的第一次调用之前创建子类的实例。这意味着对getInstance()的任何调用都将抛出getInstance()(您已基本将系统固定了)。据我所知,既不可能有注入工厂的灵活性,也不能阻止子calss的构造函数在预期方式之外被调用。

答案 1 :(得分:3)

现在,单例类的主要总体属性是防止该类的多个实例。

在您的示例中,您显然在制作SubclassSingleton的多个实例。因此,它不是单例。

SubclassSingleton x = new SubclassSingleton();
SubclassSingleton y = new SubclassSingleton();
System.out.println(x == y); // false

您正在比较x.getInstance()y.getInstance()的代码没有意义,因为您甚至没有在那里调用实例方法。您已将getInstance声明为Singleton的静态方法...

即使您正在调用实例方法,x.someMethod() == y.someMethod()并不能证明xy是同一对象……或(通常)它们是同一对象的事实等效。


那么这如何违反Singleton的单义性?

简单。

SubclassSingleton的每个实例也是Singleton ...,因为SubclassSingleton扩展了Singleton。因此,当您这样做时:

SubclassSingleton x = new SubclassSingleton();
SubclassSingleton y = new SubclassSingleton();

您已经创建了两个不同的Singleton对象。这违反了Singleton类的单例性属性。例如:

Singleton x = new SubclassSingleton();
Singleton y = new SubclassSingleton();
System.out.println(x == y); // false

答案 2 :(得分:0)

很难看到Singleton类的子类化将如何与Singleton模式的定义相矛盾,因为Design Patterns: Elements of Reusable Object-Oriented Software (1994)中Singleton的条目有一个标题为 Subleting Singleton类的部分:

  

子类化Singleton类。主要问题不是定义子类,而是安装其唯一实例,以便客户端可以使用它。本质上,引用单例实例的变量必须使用子类的实例进行初始化...

本节包括如何使用环境变量,Singleton类的注册表以及实例的延迟初始化如何完成此操作的说明。但是,它的复杂性支持了其他答案中指出的观点,尽管可能,但这还是有些奇怪的设计选择。