But in the rush to condemn, sometimes people lose precision. I can understand why, as the problems can be infuriating, but it’s important to be precise about exactly what aspects of singletons cause problems. For example, I love some of Miško Hevery’s writing on the testability implications, but he gets pretty confused when he starts talking about how static functions are bad in themselves and forgets entirely about a functional style of programming.
Steve Yegge doesn’t have a problem being precise, but he does throw in some generalisations on top:
the Singleton pattern is a throwback to non-OO programming. It’s a lifeline for people who didn’t understand a single word that the Gang of Four were trying to say. I don’t know how it got in there in the first place — some political OOPSLA pressure, no doubt — but it doesn’t belong in there. It’s Evil.
That’s fine – his writing is hilarious – but it’s a bit dangerous to imitate it without fully understanding the real substance of the argument.
Being precise is particularly important when people create non-standard singletons: classes that don’t follow the design pattern to the letter, but which have similar features (and problems). So, here are three separate aspects of singletons that I thought were worth teasing out.
Most obviously, a singleton enforces the fact that there’s a single instance of some data. The classic pattern achieves this by hiding the constructor and only creating one instance internally, but this can also be done with static data (in the extreme, an all-static class).
Now this enforcement can be good or bad. If it’s genuinely important that you only have one instance for your application, then it’s good. Using the tools of your language and having the compiler enforce a desirable invariant for your application seems like a good idea.
It’s bad, however, when the situation changes and you suddenly want more than one. It’s amazing how often you think “we could never have more than one of X” and later find that you do. If your code allows you to change your mind quickly, this doesn’t have to be a big problem. The big problem is when your mechanism for enforcing singularity precludes the change to ‘n‘ instances. Some examples of hard-to-change mechanisms include:
- A purely static class
- Using a global point-of-access, like the “getInstance” method in the classic implementation (more on this in the next section)
It’s also worth noting a great rule of thumb I heard once: never enforce the singleton property in a library that you intend for reuse. The decision on whether to allow more than one of something is an application-level one. Don’t cripple your library!
You also often hear a similar point, expressed in terms of the “Single Responsibility Principle”: enforcing the singleton property is a separate responsibility from the logic of your class, and should therefore be separate. Personally I find this to be rather abstract and weak – certainly not much use on a practical level in convincing someone who likes singletons!!
Global access point
In the classic pattern, restricting instantiation and providing a global point of access are one and the same, because they’re implemented in one mechanism. But it’s worth teasing out as a concept in its own right: you can easily create a globally-accessible, widely-used instance of any class, whether it has a single instance or not.
The argument in favour of this is essentially convenience. No matter where you are in the code, you can immediately access this functionality – no need to parameterise the current class or function with that object.
Unfortunately, this single point of access has multiple problems:
- The single-instance assumption becomes woven and entangled throughout your code. Changing to more instances in future becomes very difficult.
- Code that uses the global access point is hiding that dependency, lying about what it uses and depends on.
- Multiple global instances communicating through global access points are tricky to initialise in the correct order. Implementations typically devote a lot of effort to correct ordering of initialisation and termination – and these can get pretty contorted.
- Testing becomes harder as you can’t pass in a mock implementation to isolate components.
Dependency injection is the modern in-favour approach, with DI frameworks providing a level of convenience that might otherwise be missing.
There’s one final consideration: how stateful is the class? How much does it rely on you calling its methods in the correct order, with state carrying over from one call to influence the next? APIs that work this way are hard to use correctly, and make the calling code brittle to change.
I talked about this last time – these classes are hard to use whether or not they’re singletons. It’s just that the problems are exacerbated by how widely an object of this type is used. When it’s used as a local variable, it’s not a huge deal: it may make writing that function frustrating (as in my date example), but nothing more. At the other end of the scale is an object referenced and used widely throughout your application. A singleton is one way this can happen, but it’s by no means necessary. It could be a “normal” object that just happens to have been passed around a lot.
Here’s where just saying “singletons are evil” could be lazy thinking. Suppose you spent a lot of time removing the singleton pattern from a class: removing use of ‘static’, removing a global point of access, refactoring all code to get access another way (perhaps introducing dependency injection). Now suppose this class was highly stateful. You’d still have a huge number of problems that your hard work wouldn’t have addressed. Just a thought, but you may have been better off addressing the nature of the API before the singleton pattern!