Problems with the Singleton Design Pattern

Introduction

The design pattern Singleton first came to the attention of the software development community at large as a result of its documentation in the book Design Patterns: Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson  and John Vlissides, which I mentioned in my “A Brief Introduction to Software Patterns” article. Unfortunately, Singleton has become one of the most – if not the most – popular patterns when it is actually best avoided.

I have implemented Singleton many times (although not for some years now!). While it is just possible that there might be in existence a favourable problem/context/forces combination, I now can’t think of one occasion where my use of Singleton was a good solution to the problem I was trying to solve. Singleton is, after all, nothing more than a global variable dressed up as an object. Unfortunately, its appearance in the book by Erich Gamma et. al. – nicknamed the “Gang of Four (GoF)” – has given it an undeserved respectability. Further, looking back at the GoF’s examples of where Singleton is supposed to be a good solution, frankly I don’t think they stand up to scrutiny.

For some time now I have held the view that Singleton causes more problems than it solves. It is just unfortunate that often developers use Singleton and find workarounds for the problems it causes, without realising that the presence of these problems is really an indication that they should seek an alternative approach.

Roles and Responsibilities in Design

Having taken the swipe at Singleton, I will spend the rest of this article explaining the assertions made above. To this end I think we need to step back and ask two fundamental questions about the design of objects and their interfaces:

  • How much knowledge should an object have of the outside world in which it is being used?
  • How much responsibility should be captured within a single interface?

In answer to the first of these: an object’s knowledge of the outside world should extend to what it is told via its interface. An object’s purpose is to provide certain functionality to its clients, and to this end an object should provide the minimum useful interface that makes this functionality accessible. As a matter of design principle an object should stick to providing functionality via its interface, and not assume any knowledge of how the outside world – i.e. client code – is using it. Therefore it follows that an interface can’t make any assumptions about how many objects that support that interface are needed, because that issue is resolvable only in the context of the client code.

The matter of how much responsibility an interface should assume is somewhat less concrete. An interface should capture a single role, but it is not always easy to define what constitutes a single (cohesive) role. An interface supporting a simple Factory Method has its role is extended to incorporate the responsibility of serving up other objects with related roles. However, in the case of Singleton, the interface/semantics must assume three kinds of responsibilities: (i) the objects functionality, (ii) the serving up of the single instance, and (iii) the management of that object. Further, the whole premise of Singleton is that there can be only one instance, but the logic is flawed for one simple reason: knowledge of how many instances to create is in the client code, not the type being instantiated. A Singleton object is therefore presuming to know something it cannot reasonably know.

Encapsulation

Encapsulation – cordoning off implementation detail and making it available via a public interface – is a friend to the software designer. Encapsulation is one of the features of well designed software that brings various benefits with it: clarity in the communication of how it is intended to be used, and ease of testing, to name just two. Singleton is essentially just a global variable wrapped up as an object – and global variables are well known to be the enemy of encapsulation. It is just unfortunate that its appearance in the Design Patterns book by Erich Gamma et. al. has lead to so many software developers failing to notice the global variable in disguise aspect of Singleton.

If a function makes use of a global variable, then its inner workings can be influenced via the global variable; that is to say, the inner workings can be influenced from outside the function while bypassing the public/published interface. The same thing applies to a function that uses a Singleton object! Because the public interface can be bypassed in this manner, it is much more difficult to specify pre/post conditions for calls to such a function.

Initialisation

There is only one instance of a Singleton, so it much be initialised only once – but how can this be achieved? All the text book examples seems to dodge this question by using examples that require only default initialisation, that is to say, initialisation that does not require any arguments to be passed to the object on its construction. However, real world examples are not so simple, more often than not.

One approach is to initialise on first use. However this is only practical if the control flow to the point of first use can be predicted, and that the path through the code passes through this point only once. Alternatively, initialisation arguments can be passed to the class method that returns the Singleton’s instance everywhere this method is called; in this case the arguments would have to be ignored except on the first call to this method. Note that this approach is clearly weak, if not flawed: how do you ensure that the same argument values are passed in each case (and if you can’t the result is chaos)? Neither of these methods is pretty, and the indication is very much that Singleton is causing more problems than it is solving!

A Parameter Passing Alternative

Having made a case for Singleton being a bad idea I now need to come up with an alternative. The approach I suggest is simply the following: client code (of the functions/objects that need the single instance) should create and maintain ownership of the single instance, and pass it as an argument to the components that need it. This approach is known as Parameterise From Above. Unfortunately Parameterise From Above is a design pattern that is “out there” and has been discussed in several places (I’m not citing references here, just try Googling it) but which seems to have no formal write up – or at least no definitive write-up. Anyway, using the Parameterise From Above approach, the following are true:

  • Initialisation problems go away because the control flow on which the instance should be initialised is completely clear
  • Encapsulation is in good shape because there is no longer a “back door” that bypasses the interfaces of the functions/objects that use the single instance. Note that it is now straightforward, in a unit test, to substitute a Mock Object implementation for single instance
  • The single instance class now does not assume any responsibility for managing its single instance. That is to say, it can concentrate on providing (via its interface) the functionality that fulfils its design role, and it does not need to concern itself with responsibilities that rightfully belong in the client code that uses it – such as managing class instances!
  • The interface of the single instance class can be designed such that it assumes responsibilities that add up to it fulfilling a single role in the overall software system design
  • Although there is a downside in that the client code must maintain the single instance, believe I have shown that Singleton incurs sufficient problems that having the client code maintain one more instance (and being able to pass the instance around in a natural manner) makes this more than worthwhile
  • If the design changes so that more than once instance is needed, refactoring is far easier

Finally

The problems I have described in this article point to the Singleton design pattern being dysfunctional. That is to say, although it may solve a problem, the consequences of deploying it as a solution results in more problems than are solved, and that the approach involving Parameterise From Above offers are far more preferable set of tradeoffs.

Leave a Comment

Your email address will not be published. Required fields are marked *