There is a widely accepted opinion that Singleton is an anti-pattern. As usual, there are always exceptions to the rule. Can you explain why Singleton is a bad choice in general and give an example of some valid use cases for it?
The two main criticisms of Singletons fall into two camps from what I've observed:
- Singletons are misused and abused by less capable programmers and so everything becomes a singleton and you see code littered with Class::get_instance() references. Generally speaking there are only one or two resources (like a database connection for example) that qualify for use of the Singleton pattern.
- Singletons are essentially static classes, relying on one or more static methods and properties. All things static present real, tangible problems when you try to do Unit Testing because they represent dead ends in your code that cannot be mocked or stubbed. As a result, when you test a class that relies on a Singleton (or any other static method or class) you are not only testing that class but also the static method or class.
As a result of both of these, a common approach is to use create a broad container object to hold a single instance of these classes and only the container object modifies these types of classes while many other classes can be granted access to them to use from the container object.
I agree that it is an anti-pattern. Why? Because it allows your code to lie about its dependencies, and you can't trust other programmers to not introduce mutable state in your previously immutable singletons.
A class might have a constructor that only takes a string, so you think it is instantiated in isolation and does not have side effects. However, silently, it is communicating with some sort of public, globabally available singleton object, so that whenever you instantiate the class, it contains different data. This is a big problem, not only for users of your API, but also for the testability of the code. To properly unit-test the code, you need to micro-manage and be aware of the global state in the singleton, to get consistent test results.
The Singleton pattern is basically just a lazily initialized global variable. Global variables are generally and rightly considered evil because they allow spooky action at a distance between seemingly unrelated parts of a program. However, IMHO there is nothing wrong with global variables that are set once, from one place, as part of a program's initialization routine (for example, by reading a config file or command line arguments) and treated as constants thereafter. Such use of global variables is different only in letter, not in spirit, from having a named constant declared at compile time.
Similarly, my opinion of Singletons is that they're bad if and only if they are used to pass mutable state between seemingly unrelated parts of a program. If they don't contain mutable state, or if the mutable state that they do contain is completely encapsulated so that users of the object don't have to know about it even in a multithreaded environment, then there's nothing wrong with them.
Why do people use it?
I have seen quite a number of singletons in the PHP world. I don't remember any use case where I found the pattern to be justified. But I think I got an idea about the motivation why people did use it.
"Use a single instance of class C throughout the application."
This is a reasonable requirement e.g. for the "default database connection". It does not mean you won't ever create a second db connection, it just means you usually work with the default one.
"Do not allow class C to be instantiated more than once (per process, per request, etc)."
This is only relevant if instantiating the class would have side effects that conflict with other instances.
Often these conflicts can be avoided by redesigning the component - e.g. by eliminating side effects from the class constructor. Or they can be solved in other ways. But there might still be some legit use cases.
You should also think about whether the "only one" requirement really means "one per process". E.g. for resource concurrency, the requirement is rather "one per entire system, across processes" and not "one per process". And for other things it is rather per "application context", and you just happen to have one application context per process.
Otherwise, there is no need to enforce this assumption. Enforcing this also means you cannot create a separate instance for unit tests.
This is legit only if you have no proper infrastructure to pass objects around to the place where they are used. This could mean that your framework or environment sucks, but it might not be within your powers to fix that.
The price is tight coupling, hidden dependencies, and everything that's bad about global state. But you are probably already suffering these.
This is not a necessary part of a singleton, but it seems the most popular way to implement them. But, while lazy instantiation is a nice thing to have, you don't really need a singleton to achieve that.
The typical implementation is a class with a private constructor, and a static instance variable, and a static getInstance() method with lazy instantiation.
In addition to the problems mentioned above, this bites with the single responsibility principle, because the class does control its own instantiation and life cycle, in addition to the other responsibilities that the class already has.
In many cases, you can achieve the same result without a singleton, and without global state. Instead, you should use dependency injection, and you might want to consider a dependency injection container.
However, there are use cases where you have the following valid requirements remaining:
- Single instance (but not single instantiation)
- Global access (because you are working with a bronze age framework)
- Lazy instantiation (because it is just nice to have)
So, here is what you can do in this case:
Create a class C that you want to instantiate, with a public constructor.
Create a separate class S with a static instance variable and a static S::getInstance() method with lazy instantiation, that will use class C for the instance.
Eliminate all side effects from the constructor of C. Instead, put these side effects in the S::getInstance() method.
If you have more than one class with the above requirements, you may consider to manage the class instances with a small local service container, and use the static instance only for the container. So, S::getContainer() will give you a lazy-instantiated service container, and you get the other objects from the container.
Avoid calling the static getInstance() where you can. Use dependency injection instead, whenever possible. Especially, if you use the container approach with multiple objects that depend on each other, then none of these should have to call S::getContainer().
Optionally create an interface that class C implements, and use this to document the return value of S::getInstance().
(Do we still call this a singleton? I leave this to the comment section..)
You can create a separate instance of C for unit testing, without touching any global state.
Instance management is separated from the class itself -> separation of concerns, single responsibility principle.
It would be quite easy to let S::getInstance() use a different class for the instance, or even dynamically determine which class to use.
Personally I will use singletons when I need 1, 2, or 3, or some limited amount of the objects for the particular class in question. Or I want to convey to the user of my class that I do not want multiple instances of my class being created for it to function properly.
Also I will only use it when I need to use it almost everywhere in my code and I don't want to pass an object as a parameter to each class or function that needs it.
In addition I will only use a singleton if it does not break another function's referential transparency. Meaning given some input it will always produce the same output. I.e. I don't use it for global state. Unless possibly that global state is initialized once and never changed.
As for when not to use it, see the above 3 and change them to the opposite.