JaVa
   

Evil Singletons

A special case of the, How do I get the dummy into the object? problem are singletons. The popularity of design patterns [Gamma95] in today's coding community has led to a situation where simple patterns are commonly used without previously weighing their drawbacks. A singleton represents the simplest of all commonly used patterns. It is supposed to ensure that only one instance of a specific class is created and that this instance is easily accessible by all objects within the system. This approach appears practical for objects used across a system, such as resource management systems, database systems, pre-settings, and generally all global objects that the developer would like to have freely available everywhere and always. But caution is needed here, because singletons used without much thought given are nothing more than global variables in object-oriented systems, along with all their drawbacks, like sensitivity to side effects and weakened encapsulation [Rainsberger01]. Also, if singletons are heavily used in app servers, we are often confronted with unexpected problems caused by the use of threads and app-specific class loaders. But enough of this! We do not want to discuss the issue "Singletons are evil," [2] but instead to study a test-specific problem: Considering that there is exactly one instance of each singleton class at a program's runtime and that there is only read access to that instance, the question is how can we replace this instance by a mock instance, if necessary. The following solution appears feasible (this time, our singleton class abstracts from all meaningful activities):

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


Our mock singleton as subclass can now offer an initialization possibility for test purposes:

public class MockSingleton extends Singleton {
 public static void initMockSingleton() {
 instance = new MockSingleton();
 }
}


In our tests, we have to ensure that the initMockSingleton() method is executed at the beginning of the test or in the setup:

public class MockSingletonTest extends TestCase {
 public void testInitialization() {
 MockSingleton.initMockSingleton();
 assertTrue(Singleton.getInstance()
 instanceof MockSingleton);
 }
}


This is a feasible method for us, if we really don't want to part with our singleton, but it has several drawbacks:

Both problems can be solved by providing a setter method for the singleton. Naturally, the singleton would then no longer be a real singleton; it would have mutated into a dangerous species: a globally accessible state container, subject to all kinds of side effects. There is a way out of this singleton crisis, because another concept hides behind most singletons, but escape is not free: we need objects that remain the same within a specific context and exist only once. This context can be our system, our user, or perhaps our session. So why not use a system object, a user object, or a session object that will give us access to the objects we would otherwise have turned into singletons? We could then either pass this system object to all objects that need it when we create them, or—being ready for a compromise—turn them into singletons. This way, we will be sitting on only one single singleton in the end, but we will have to watch it closely.

This discussion shows again that the wish for local testability challenges common coding patterns, drawing our attention to design problems that we might otherwise have simply overlooked or ignored. Creating software based on the test-first approach lets us avoid most of the difficulties in advance. On the other hand, trying to equip an existing app with a dense mesh of developer tests in arrears will take us to a point where the implementation of these tests will demand extensive program restructuring actions. We hardly dare say it again: careful balancing between cost and benefit is mandatory to avoid many months of restructuring effort. [2]This issue is discussed at [URL:CoSingle] and [URL:WikiSAE] in detail.


JaVa
   
Comments