Steve Love explores how 'Singletons' in design can seem a good idea at the time, why they are generally a mistake, and what to do if you have them.
For a while now, it has become increasingly accepted that the use of Singleton is a Bad Thing™. In general terms we hear the case against Singletons: hard to test; don't play nicely with multiple threads; hide dependencies; etc. By and large, I think, programmers are becoming less enamoured with Singletons, and this, I also think, is a Good Thing™. But it's not as widespread as I thought. Singleton is still pretty popular: it is, after all, simple to implement. Programmers use it with the very best of intentions, even when those intentions are to find an alternative to real global data.
Instead of trotting out the same arguments that others have used, however, I want to explore a little, and implement a small system in terms of Singleton and see where it leads. This (I hope) will also allow some exploration into how to return from Singleton-ness to a more ordered world.
The code examples here are in C#, mainly because it's compact and simple. There shouldn't be anything that can't easily be translated to Java, C++, or others.
The descent
For the sake of argument, and because it makes the code simple, the example application is a data transformation program; it takes some input, and writes a different output.
A simple Main function might contain:
// Create a new reader using the first command-line // argument as a file name. Reader reader = new Reader( args[ 0 ] ); // Create a new writer which performs the // transformations Writer writer = new Writer(); // Read all lines from the reader. null gets returned // when EOF is reached. Write resulting text to the // output. string text; while( ( text = reader.Read() ) != null ) { writer.Write( text ); }
Now this may or may not be grand design, but it suffices to provide a framework within which to think about the design. The Reader object may be doing some kind of validation of input, and the Writer collating and transforming the data.
At some point in the development of this application, the requirement arises that a run report is produced, indicating errors such as opening the file, and a record of the number of lines of text read: a logging facility for the Reader.
Having added such a facility, it doesn't take much imagination to see that the Writer component could also use the logging facility, and that making it easily accessible from anywhere in the program would ease development.
The fall
A key intent of the Singleton pattern is that it provides access to some service or data globally within an application, i.e. that it is always easily available [ 1 ]. Making a Singleton logging class in C# is straightforward:
public sealed class Logging
{
public static readonly Logging Instance =
new Logging( 'log.txt' );
public void Write( string text )
{
using( StreamWriter log = new StreamWriter(
filename, true ) )
{
log.WriteLine( text );
}
}
private Logging( string fileName )
{
this.fileName = fileName;
}
private string fileName;
}
This allows the Writer class to perform the following trick in its Write method (for the sake of the example, Writer sends its output to a file) :
public void Write( string text ) { try { // some processing on text for transforming it // ... output.WriteLine( text ); } catch { Logging.Instance.Write( 'Failed to write a line' ); } }
A similar Read method in the Reader is also achievable, and does much the same thing, with no changes to the client code.
A nasty knock
Our little neat design works as intended, and all is fine with the world, until one day, the requirements ask for multiple Writer s operating concurrently to speed up processing. As well as writing to a back-end store, results need to be transferred to some other location as a backup, in a different format. Instead of actually exposing multiple Writer objects in the main application, the existing Writer implementation spawns a thread for each target, writing its results. The client code has no need to know of the change. Each separate thread looks like the original Write and the application appears to run no slower than the original, which is handy because that was also a requirement J . All is fine with the world...
Until someone inspects the log files and sees gibberish!
After perhaps only a little head-scratching, it becomes obvious that the problem is concurrent access to the logging facility. In C# we have a handy short-hand for obtaining a lock on some object to provide a critical section, so our logging Write method now becomes:
public void Write( string text ) { lock( this ) { using( StreamWriter log = new StreamWriter( filename, true ) ) { log.WriteLine( text ); } } }
The lock statement effectively allows only a single thread of execution into the protected code at a time. A second thread cannot enter that section of code until the first thread completes it and releases the lock.
So the problem of the gibberish log has been fixed. However...
The hard landing
All calls into the logging component are now so tightly synchronised that any benefit of using multiple threads is lost. Whilst thread one is logging its output, thread two must wait until it has finished before continuing and vice-versa. This code probably performs worse than the single-threaded alternative due to the overhead of obtaining the lock in each thread.
One solution to this would be to make the logger entirely asynchronous. A call to Logging.Write would merely add the message to a queue (which itself would have to be locked each time, but the latency would be greatly reduced). The logging component would have a thread of its own looking for available messages to actually write to the output. This seems neat and tidy, but suffers mainly from the fact that the logging facility is now many times more complicated, introducing a large potential for error, and additionally is much more complex than the code that uses it!
...and kicked when you're down
As work is under way trying to 'fix' the logging problem, some users report that the transformed output sometimes contains errors. Initial investigation suggests a bug in the Writer component, but reproducing the error is hard, making the location of the error hard to pin down. It seems the only course of action is to run the entire application in a debugging environment, and keep trying this with different input until the bug is found. Not only is this time consuming, it's boring for the poor soul selected to do it - and that makes it error-prone, too.
What would be really nice is to be able to test the Writer component in splendid isolation, away from the rest of the application, under laboratory conditions, and just have it perform its tricks on small sets of test input.
To achieve this separation entirely, the Writer component needs to be stripped of all its dependencies. For true lab conditions the Writer object needs to exist in a test harness that might look like this:
public void TestInputsToOutputs() { Writer writer = new Writer(); writer.Write( 'test text' ); // How do we test the output here? // Perhaps open the output file, and check its // contents? }
It has now become clear that Writer has some hidden dependency which we need to break: Writer sends its output to file each time its Write method is called - a hidden dependency! In our ideal test, we'd like to be able to tell the Writer where to send its output, so our test can become (for example):
public void InputsToOutputs() { TextWriter output = new StringWriter( new StringBuilder() ); Writer writer = new Writer( output ); string testText = 'test text'; writer.Write( testText ); Assert.AreEqual( testText, output.ToString() ); }
This associates a Writer object with the output target as a TextWriter - an abstract base class for both StringWriter used in the test, and a StreamWriter which can be used to write to a file.
The key point here is that Writer is parameterised with the needed dependency. Obviously the dependency itself has not been removed altogether - that would be pointless because it removes needed functionality. Instead the dependency is moved from a concrete class to an interface, and a reference to the object is passed in . Amongst other things, this is called Dependency Inversion , achieved using a technique called Parameterise From Above [ 3 ] [ 4 ].
A healing injection
Having unhidden the dependency on an actual file location, the Writer object still has the dependency on the logger. Unless we need to test the logged output, this dependency isn't a great problem. It isn't ideal, though.
Firstly the tests are slowed down by the need to write to the logging component. In the test environment, there is no need for logged output, so this is merely a waste. The second issue is one of style: the object under test cannot be tested in true isolation.
Resolving the dependency is harder for this case than for the output location, because the dependency is upon a Singleton. We could re-factor the code to pass in the Logging component, having first modified the Logging class to be an implementation of some interface. Given that we have no tests to ensure that this refactoring is successful (that's what we're trying to achieve!), this approach is somewhat risky.
A different approach is to inject the dependency into the object. The Parameterise From Above Pattern used just now is an example of injecting the dependency into the user-object. What if we could inject the Singleton object to allow us to control its dependency on a real live log file? This would allow us to keep the existing code - which reduces the risk of breaking the application - but does muddy the waters a little by adding indirection to the Logging component. In its very simplest form, the new logging component might look like (please remember this is for brevity!):
public interface LoggingInterface { void Write( string text ); } public sealed class DefaultLogging : LoggingInterface { public void Write( string text ) { // The actual writing to log file goes here } } public sealed class Logging { public static LoggingInterface Instance = new DefaultLogging(); }
The idea here is that the class actually writing to the log file now implements a common interface and the Singleton itself just passes requests off to the implementor. It is possible to assign a new implementation to this variable, as long as it implements LoggingInterface [ 8 ].
Test code can now look like:
class NullLogger : LoggingInterface { public void Write( string text ) { } } [Test] public void InputsToOutputs() { TextWriter output = new StringWriter( new StringBuilder() ); Writer writer = new Writer( output ); // Inject a do-nothing logger into the singleton Logging.Instance = new NullLogger(); string testText = 'test text'; writer.Write( testText ); Assert.AreEqual( testText, output.ToString() ); }
This is not entirely nice, and the dependency on the Singleton remains, but there is still value here: note that the code that uses the Logging class requires no changes. This therefore gives you enough of a framework to write tests for the Writer class, which in turn allows you to refactor the Logging and Writer classes more safely at a later date, perhaps to use the Parameterise From Above pattern described before.
Healing time (multiple remedies)
There are other ways to isolate and manage the Singleton Dependency Problem, and what you end up using depends very much on your circumstances (doesn't everything? There's no substitute for using your brain!).
One simple technique is to have some globally accessible Factory [ 1 ], which can be asked for services. In simple form, this may manifest itself as a simple Global Service Locator [ 2 ], such as:
public sealed class ServiceLocator { public LoggingInterface Logging { get{ return logging; } } // Other application services // ... }
This of course bears a striking resemblance to the Singleton we've already seen, but it has the benefits that access to the services provided is all in one place, and that ServiceLocator can be easily parameterised by injection for testability, etc., using Dependency Injection. It does still have the same inside-out dependency that accessing a Singleton instance has, and the dependency is still hidden.
This form of Service Locator is a kind of Application Context [ 5 ]. It is an easily accessible single point of contact where different parts of an application can access the common context needed to run. It doesn't take much imagination to see that we could combine this with Parameterise From Above so that instead of the dependency being inside-out, it becomes outside-in again:
public class Writer { public void Write( string text, ServiceLocator context ) { // ... context.Logging.Write( 'Failed to write a line' ); } }
One of the motivating reasons behind Encapsulated Context is that using Parameterise From Above can cause over-long parameter lists. This can certainly be a problem where you require several services, and many objects need access to the majority of them, although this should rarely be the case. However, using a single Encapsulated Context object which covers all of an application's needs can appear to be little better than lots of (possibly Singleton) Service objects.
Splitting a single multi-purpose Encapsulated Context into different Role-Partitioned Context objects [ 6 ] can alleviate this problem, and has the added benefit of allowing the code to directly publish its immediate dependencies, and provide a certain level of in-code documentation, too.
The small example used here would not really benefit from either Encapsulated Context or Role-Partitioned Context , and a Service Locator would be too heavy-weight. However, few real-world applications have the luxury of being so simple.
The emergence
If we had written our little example using a Test First approach, as advocated by Test Driven Development [ 7 ], I think it likely we would have ended up with an interface to represent the Logging object being passed in to the Writer object - Parameterise From Above . It is the simplest approach that fulfils all our requirements: the Writer object logs to a file in the real application, and is easily testable in isolation from other objects.
As further requirements became apparent, it may have become more useful to use Encapsulated Context or Role-Partitioned Context objects to pass in the needed services. Unless the requirements really need several independent services, however, Parameterise From Above is superior on the basis of its simplicity.
I remain suspicious of Global Service Locator partly because the word 'global' conjures the wrong connotations for me, but mainly because it has many of the hallmarks (and problems) of Singleton.
A return
Our little journey has taken us through a design that, though very simple - even trivial - lent itself to a very plausible solution using the Singleton Design Pattern, and highlighted some of the difficulties resulting from this decision - however well-intentioned. There are other issues with Singletons: they can be particularly difficult to manage in C++, where the tension between lifetime management and the use of static data members causes real headaches. In this, maybe C# does us a dis service making Singletons so easy to implement.
As for the alternatives? Well, Parameterise From Above and Dependency Injection both are often criticised for causing contorted, spaghetti logic caused by over-long parameter lists. This criticism can often be overcome by judicious use of Encapsulated Context and its relatives, and if it cannot be overcome that way, that could be an indication of a flaw in the design. Using Singletons or globals to reduce parameter lists are non-solutions because they don't reduce the dependencies in the code - they just hide them. If long parameter lists are the symptom, high coupling is the likely disease.
The technique of allowing the Singleton itself to have its Instance changed by client code is a very useful one when working with legacy code for which you want to introduce unit tests. It's probably not recommended for new code.
However, regardless of implementation language, problems with lifetime, threading or hidden dependencies, and irrespective of difficulties with testing, there are very few reasons to use Singletons anyway. It's important to understand the difference between needing a single instance of some object or service right now , and the necessity that there is only one possible instance of some object or service [ 9 ]. Consider the need to use a Stub Object for the logging object - even a test can be an important instance of an object.
Acknowledgements
The motivation for this article arose from a discussion on the accu-general mailing list, which was really focused on replacing instances of Singleton objects with something else. Several somethings else were suggested by various respondents. In particular, John Jagger suggested being able to inject a Singleton instance with a different implementation (his version was much tidier than mine J ); Hubert Matthews suggested the Factory Method Service Locator , and Adrian Fagg suggested Encapsulated Context , with references to detailed information on it and variations on it provided by Allan Kelly and Kevlin Henney.
Notes and references
[ 1] Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides, Design Patterns - Elements of Reusable Object Oriented Software , Addison Wesley, 1995
[ 2] Martin Fowler, `Inversion of Control Containers and the Dependency Injection pattern' http://www.martinfowler.com/articles/injection.html
[ 3] Kevlin Henney, `Minimalism: A Practical Guide to Writing Less Code', 2002, http://www.two-sdg.demon.co.uk/curbralan/papers/jaoo/Minimalism.pdf
[ 4] Kevlin Henney, `Programmer's Dozen', 2003, http://www.two-sdg.demon.co.uk/curbralan/courses/ProgrammersDozen.pdf
[ 5] Alan Kelly, `Encapsulated Context Pattern', http://www.allankelly.net/patterns/encapsulatecontext.pdf (printed in Overload 63 (October 2004) as `Encapsulate Context')
[ 6] Kevlin Henney, `Context Encapsulation', http://www.two-sdg.demon.co.uk/curbralan/papers/europlop/ContextEncapsulation.pdf
[ 7] Kent Beck, Test Driven Development By Example , Addison Wesley, 2002 (See also http://www.testdriven.com )
[ 8] Even with readonly left in, it's actually possible to assign to this variable, but I that too counter-intuitive even for an example!