从这里的其他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
是的,从技术上讲x
和y
是Singleton
的实例,但是获取它们的实例仍然会导致共享资源,并且在构造{{1 }}基本上没有任何作用-静态方法x
将返回父类的共享结构。因此,我很难理解,尽管被子类化了,但是这本质上违反了拥有getInstance()
一个共享实例的行为。
答案 0 :(得分:4)
tl; dr :虽然我认为可以以可以将其子类化的方式设计单例,但是在设计类时必须格外小心。在大多数情况下,我认为这样做是不值得的。
我不会说子类化是一种固有的违反单例原则的方法,但是在设计此类时必须格外小心。要了解原因,让我们看看这个问题中的相反定义。
第一个是定义Singleton Pattern。本质上说,对于该类,该类最多会有一个实例,并且所有类都使用同一实例。在Java中,这通常意味着无法重新初始化单例(即,必须避免再次调用new ...
)。为什么?因为您程序中的某个地方,某些部分可能包含对“旧”单例实例的引用x
。当您重新初始化单例时,x
仍将引用旧的单例,而将来对单例访问器的所有调用将保留对新单例的引用。您有两个相同类的实例,因此您违反了单例。
与单例wrt相反的定义。子类是Liskov Substitution Laws。它从本质上说,类X
的每个子类都必须在X
处可用,并且程序仍然可以工作。这就是为什么Square
和Rectangle
永远不应该成为继承关系的原因(一个只需要定义一个属性[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()
并不能证明x
和y
是同一对象……或(通常)它们是同一对象的事实等效。
那么这如何违反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类的注册表以及实例的延迟初始化如何完成此操作的说明。但是,它的复杂性支持了其他答案中指出的观点,尽管可能,但这还是有些奇怪的设计选择。