Singletons are great! But only as long as they are used correctly. From what I can tell, most people use them when they need easy access to a class from anywhere in their code. Just make it a singleton, and you can call MyManager.Instance from anywhere to get a reference. This is not a good reason to use a singleton, and if this is your only purpose you should use a different approach.
However if what you want is to make sure there is only one instance of a class at any time, then the singleton is the correct solution. For example if you have some limited system resource, it makes sense to have just one instance of a class for interacting with it. The most common, but perhaps not the best example, is the log writer. You only have one log file, so it makes sense that all interaction with the log goes through a singleton class.
If you only want easy access to an object from anywhere in your code, you are better off using some kind of inversion of control. This is your Service Locator or Dependency Injection. You don’t actually need to use any of these to have inversion of control, simply passing the object as a parameter could also be considered inversion of control.
In my opinion, this is not a solved problem in object oriented programming. Dependency Injection is the most recent attempt at solving this, but for me I don’t like how you need to set up all the relations in a single place, and then have to rely on black magic for the references to be resolved in all your other classes.
My favorite solution is to use “manual” inversion of control, and just pass the objects along in the constructors. Yes it might make the parameter list a bit longer, but I don’t really see this as a big deal. The problem with doing this in Unity though is that classes derived from MonoBehaviour shouldn’t have constructors, and it really doesn’t fit with Unitys component based paradigm either.
I’ve had my bit of back-and-forth on this, I’ve tried singletons, dependency injectors, service locators and just setting references in the inspector. I’m currently using the service locator pattern in Unity, and it works reasonably well. Managers just register/unregister with the locator in OnEnable/OnDisable, and I can have single or multiple instances registered.
It works, it lends itself well to testing as long as I use interfaces, and the flow is relatively easy to follow. It’s no silver bullet though, so if anyone comes up with a better solution I’m all ears.