Yes, singletons are bad. They are bad because everything they do for you combines two properties, each of which is bad in about 95% of cases. (Which would mean that on average single numbers are bad 99.75% of the time;))
The syntax defined by GoF is a data structure that:
- Provides global access to the object and
- Ensures that only one instance of an object can ever exist.
The first is usually considered bad. We do not like globals. The second is a little more subtle, but as a rule, there are practically no cases where this is a reasonable restriction to ensure compliance.
Sometimes, it makes sense to have only one instance of an object. In this case, you decide to create only one. You do not need a singleton to enforce it.
And, as a rule, even when it “makes sense” to have only one instance, in the end, it makes no sense. Sooner or later you will need more than one registrar. Or more than one database. Or you will need to recreate resources for each of your unit tests, which means that we must be able to create them as we see fit. Prematurely removing the flexibility of our code before we understand the consequences.
Singlets hide dependencies and increase communication (each class can potentially depend on a singleton, which means that the class cannot be reused in other projects unless we reuse all of our singleton), and since these dependencies are not immediately visible (as a function / constructor), we don’t notice them and usually don’t think about it when we create them. It’s so simple to just pull in a singleton, it acts almost like a local variable and that’s it, so we tend to use a lot of them as soon as they are there. And that makes them almost impossible to remove again. You end up, maybe not with spaghetti code, but with spaghetti dependency graphs. And sooner or later, your runaway dependencies will mean that single games start depending on each other, and then you get circular dependencies when you try to initialize.
They make it extremely difficult for unit testing. (How do you test a function that calls functions on a singleton object? We don’t want the actual singleton code to execute, but how to prevent this?
Yes, singletons are bad.
Sometimes you really need a global one. Then use global, not single-line.
Sometimes, very very rarely, a situation may arise when creating multiple instances of a class is an error, where it is impossible to do without causing errors. (The only case I can think of, and even this is far-fetched, is if you imagine some kind of hardware device. You only have one GPU, so if you are going to match it with an object in your code, it makes sense that only one instance can exist). But if you find yourself in such a situation (and again, for emphasis, a situation where several instances cause serious errors, and not just a situation where “I can’t think of any use cases for more than one instance”), then apply this is a limitation, but do it without making the object globally visible.
Each of these two properties can be useful, in rare cases. But I can not think of a single case where a combination of them would be good.
Unfortunately, many people have the idea that "singletones are globally compatible with OOP." No, they are not. They still experience the same problems as global ones, in addition to introducing some others that are completely unrelated. There is absolutely no reason to prefer a singleton over a plain old global one.