6.9 Evil Singletons


6.9 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 programming 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 application servers, we are often confronted with unexpected problems caused by the use of threads and application-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:

  • We always have to ensure that each test puts all required singletons into the correct test state at the beginning, and then replaces them with the original. If we forget this—say, for a newly added singleton—we may have to face painful debugging sessions.

  • Sometimes, each test requires an individually configured instance of our mock singleton. This could lead to a situation where initialization code piles up in the singleton or MockSingleton class.

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 programming 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 application 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.




Unit Testing in Java. How Tests Drive the Code
Unit Testing in Java: How Tests Drive the Code (The Morgan Kaufmann Series in Software Engineering and Programming)
ISBN: 1558608680
EAN: 2147483647
Year: 2003
Pages: 144
Authors: Johannes Link

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net