Singleton is not an anti-pattern

Piotr Chmielowski
3 min readMay 27, 2018

--

The usage of the singleton pattern is very often considered as a bad practice.
In this article I’ll try to convince you that is not.

There are two main arguments against this pattern, let’s discuss them one by one.

Argument 1. Code that uses singletons is hard to test

Indeed, testing following method in isolation is nearly impossible:

void login(String user, String password) {
try {
UserManager.getInstance()
.login(user, password);
showMessage(“Login successful”);
} catch (InvalidPasswordException e) {
showMessage(“Wrong password”);
}
}

However, is it hard to test because UserManager is a singleton? I don’t think so. Let’s make it ordinary, non-singleton class and check if testing is easier:

void login(String user, String password) {
try {
new UserManager()
.login(user, password);
showMessage(“Login successful”);
} catch (InvalidPasswordException e) {
showMessage(“Wrong password”);
}
}

As you can see, from the testability point of view, nothing has changed (maybe now the situation is even worse because with the first snipped we were able to inject mocked class with some dirty trick…).

There is only one correct technique to refactor our login() method to be more testable — dependency injection. For the sake of simplicity I’ll inject UserManager as a method parameter, however in the real world application I’d probably inject it in the class which the login() method is part of:

void login(String user, String password, UserManager manager) {
try {
manager.login(user, password);
showMessage(“Login successful”);
} catch (InvalidPasswordException e) {
showMessage(“Wrong password”);
}
}

Now, from the testability point of view, there is really no difference in production code if UserManager is a singleton or not:

login(
"jane.doe@domain.com", "dolphins", UserManager.getInstance()
);
login(
"jane.doe@domain.com", "dolphins", new UserManager()
);

Of course, now the method which makes the call to login() is untestable.

We have to perform the same refactoring steps as before and end with the situation where call to UserManager’s constructor or static getInstance() method is performed only on the top level of our dependency graph (main() method for example).

Argument 2. Mutable singleton is just a mutable global variable

This is true. However it is hard for me to fully agree with the statement: global mutable state is evil. In my opinion, it just a half-truth. The full one is:

a mutable state is always problematic, the bigger the scope is, the more problems we can encounter

Let me illustrate it. This is saying mutable state is kinda OK, global mutable state is evil:

And this is what I believe: mutable state is always problematic. Singleton is not a special case:

Of course, as you can see on the second picture, singleton indeed is an extreme case.
However I’d like to emphasize that singleton pattern is not the only one that I’d put on the far-right on the axis: I’d put there every class which has a application scope — even if it does not implement the singleton pattern.

In the other words — if you create one instance of a mutable non-singleton class (by calling its constructor) and inject it everywhere, be aware that you may have problems with reasoning about current state.

Moreover, if you create two instances of this class and inject one of them in ten places and second one in another five — you still have to be careful about all side effects of mutating the state.

Conclusion

Let’s summarize quickly my opinion about the singleton. There is nothing wrong with the pattern itself — it is just a technique for making sure that there is only one instance of the class.

All the problems which are often being mentioned during discussions on singletons are kind of independent from the pattern itself:

  • testability problems are caused just by the lack of dependency injection
  • mutability is an orthogonal problem: even if you are not using singleton pattern, you can have problems with mutable state.
    However you can also have many singletons in your code base but keep most of the classes immutable and your code would be very easy to reason about.

--

--