tl;dr: While I think it is possible to design a singleton in such a way that it can be subclassed, the class must be designed with special care. In most cases, I think it is not worth the effort.
I would not say that subclassing is an inherent violation of the singleton principle, but some extra care has to be taken when designing such a class. To understand why, let us take a look at the opposing definitions in this problem.
The first one is the definition is the Singleton Pattern. It essentially statest that, for this class, there will be at most one instance of that class, and all classes use that same instance. In Java, this typically means that the singleton cannot be re-initialized (i.e. a second call to new ...
must be prevented). Why? Because somewhere in the program, some part could hold a reference x
to the "old" singleton instance. When we re-initialize the singleton, x
will still refer to the old singleton, while all future calls to the singleton's accessor will hold a reference to the new singleton. We have two instances of the same class, therefore we violate the singleton.
The definition opposing the singleton wrt. subclass is the Liskov Substitution Laws. It, in essence, states that every subclass of a class X
must be usable in X
's place, and the program should still work. This is why a Square
and a Rectangle
should never stand is an inheritance relation (one needs only one property to be defined [length
], while the other needs two [width
& height
]).
This means that, for the scope of the singleton, all subclass-instances count as instances of the superclass as well. We have to control not only instantiation of the class in question, but also of all (!) its subclasses, even the ones we have not under control. Worst case scenario we do not even know all classes that subclass our singleton.
I coded up a little example that covers the cases I can see. Note that this example is not guaranteed to be perfect, it just shows to what extend we have to go to get our Singleton
subclassable properly.
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 " + e);
}
}
}
class SubSingleton extends Singleton {
public static final Supplier<SubSingleton> SUB_SINGLETON_FACTORY = SubSingleton::new;
private SubSingleton() {}
}
Ideone example
The double-checked locking at two instances (getInstance()
and the constructor) is in deed necessary, if the call to getInstance()
is performance critical, i.e. we cannot afford to lock each call to it. Furthermore, if the singleton is used as intended, a call to getInstance()
should never throw. In the constructor, we need the double-checked locking to enforce the singleton property. In getInstance()
, we need it to prevent IllegalStateException
s from being thrown prematurely.
Seeing the above proof of concept, it is apparent that some care must be taken. There is still the possibility that somewhere in our program, an instance of a subclass is created before the first call to getInstance()
is executed. This would mean that any call to getInstance()
will throw an IllegalStateException
(we have basically bricked the system). Setting INSTANCE
from Singleton
's constructor (INSTANCE = this
) is not an option since this
is - at this point in time - a premature object.
As far as I know, there is no possibility to both have the flexibility of injecting the factory and prevent constructors of a subcalss being called outside the intended way.