This is a quick followup to my post about singletons in Cocoa apps. Read it first for context.
Another developer asked me a few questions about the points I made in my previous post on singletons. I’d like to post my (lightly edited) comments here, for the record:
I can’t see how creating this one instance and passing a pointer to interested clients decouples anything. Doesn’t it just move the responsibility of keeping track of this one object from the class to some other object or objects? And who should coordinate this ownership? What is inherently wrong with placing this responsibility with the class of the shared object, which is a singleton in itself? Isn’t it rather convenient, or at least perfectly acceptable, that it is the class object which takes on this role?
It is very convenient (and therein lies some danger).
Creating one instance somewhere and passing a pointer in decouples one class from knowing that the other is implemented as a singleton. This is a subtle but important distinction.
If a client class uses a singleton via dependency injection, it doesn’t know that class is a singleton. When you want to reuse that client class in a different project, or even in a different part of the same codebase, you only need to pass in an object with a similar interface. If the client class just grabs a singleton instance from a static global variable, that exact singleton class must come along as well.
To make this even more explicit, I might go so far as recommending that your singleton’s entire public interface (expect for the
sharedInstance method) be specified in a protocol instead of as part of the
@protocol CDZNetworkInfoMonitor // current network access technology, reachability, etc. accessible through this protocol @end @interface CDZNetworkInfoMonitorSingleton <CDZNetworkInfoMonitor> + (instancetype)sharedMonitor; @end
Then, classes which need to get the shared instance (most often view controllers and the app delegate) would
#import "CDZNetworkInfoMonitorSingleton.h", but most other classes (models, views, the model controller) would just
#import "CDZNetworkInfoMonitor.h". Those client classes then receive a monitor
id<CDZNetworkInfoMonitor> via dependency injection wherever necessary, but they themselves are not actually dependent on global state.
Singletons often lend themselves to SRP [Single Responsibility Principle] violations. These violations are one of the biggest problems I see in most singleton classes.
The whole point of using singletons, in my opinion, is to unambiguously place the responsibility with a single object. The statement above almost contradicts the earlier stated desire to leave the ownership (or lack thereof) to arbitrary objects that happen to be using the singleton. Letting the class be the owner of the singleton, and letting it provide an easy access to it, is clear SRP to me.
I may not have been particularly clear here. You’re right that “The whole point of using singletons, in my opinion, is to unambiguously place the responsibility with a single object.”
What I mean is that singletons are very convenient places to put logic and state because they’re (by design) trivially globally accessible. You then must be careful not to use a singleton as a dumping ground for disparate logic and state.
All I’m arguing on this point is that, like any class, a singleton should have a clearly defined single responsibility. It’s just even more important with singletons, because global accessibility makes it much easier & more tempting to dump random homeless stuff into one.