I know well that you want to use Singleton to provide a global point of access to some state or service. The benefits of the Singleton pattern do not need to be enumerated in this question.
What I am interested in are the situations when Singleton might seem like a good choice at first, but might come back to bite you. Time and time again, I've seen authors in books and posters on SO say that the Singleton pattern is often a very bad idea.
The Gang of Four states that you'll want to use Singleton when:
- there must be exactly one instance of a class, and it must be accessible to clients from a well-known access point.
- when the sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code.
These points, while certainly notable, are not the practical ones w开发者_运维知识库hich I seek.
Does anyone have a set of rules or caveats that you use to assess whether you're really, really sure you want to use a Singleton?
Summary Version:
You know how often you use globals? Ok, now use Singletons EVEN LESS. Much less in fact. Almost never. They share all the problems globals have with hidden coupling (directly impacting testability and maintainability), and often the "only one can exist" restriction is actually a mistaken assumption.
Detailed Answer:
The most important thing to realize about a singleton is that it is global state. It is a pattern for exposing a single instance of globally unmitigated access. This has all of the problems in programming which globals have, but also adopts some interesting new implementation details and otherwise very little real value (or, indeed, it may come at an unnecessary extra cost with the single instance aspect). The implementation is different enough that people often mistake it for an object oriented encapsulation method when it is really just a fancy single instance global.
The only situation in which you should consider a singleton is when having more than one instance of already global data would actually be a logical or hardware access error. Even then you should typically not deal with the singleton directly, but instead provide a wrapper interface which is allowed to be instantiated as many times as you need it to be, but only accesses global state. In this manner you can continue to use dependency injection and if you can ever unmarry global state from the behavior of the class it isn't a sweeping change across your system.
There are subtle issues with this, however, when it appears as if you are not relying on global data, but you are. So that (using dependency injection of the interface which wraps the singleton) is only a suggestion and not a rule. In general it is still better because at least you can see that the class relies upon the singleton whereas just using the ::instance() function inside the belly of a class member function hides that dependency. It also allows you to extract classes relying on the global state and make better unit tests for them, and you can pass in mock do-nothing objects where if you bake reliance on the singleton directly into the class this is MUCH more difficult.
When baking a singleton ::instance call which also instantiates itself into a class you make inheritance impossible. Work-arounds typically break the "single instance" part of a singleton. Consider a situation where you have multiple projects relying on shared code in a NetworkManager class. Even if you want this NetworkManager to be global state, and single instance, you should be very skeptical about making it into a singleton. By creating a simple singleton which instantiates itself you are basically making it impossible for any other project to derive from that class.
Many consider the ServiceLocator to be an anti-pattern, however I believe it is a half step better than the Singleton and effectively eclipses the purpose of the Go4 pattern. There are many ways to implement a service locator, but the basic concept is that you break up the construction of the object and the access of the object into two steps. In this way, at runtime, you can connect the appropriate derived service, and then access it from a single global point of contact. This has the benefit of an explicit object construction order, and also allows you to derive from your base object. This is still bad for most of the stated reasons, but it is less bad than the Singleton and is a drop-in replacement.
One specific example of an acceptable singleton(read: servicelocator) may be in wrapping a single-instance c style interface like SDL_mixer. One example of a singleton often naively implemented where it probably shouldn't be is in a logging class (what happens when you want to log to console AND to disk? Or if you want to log subsystems separately.)
The most important problems of relying on global state, however, pretty much always come up when you're trying to implement proper unit testing (and you should be trying to do that). It becomes so much harder to deal with your application when the bowels of classes that you don't really have access to are trying to do unmitigated disk writing and reading, connect to live servers and send real data, or blast sound out of your speakers willy nilly. It's much, MUCH, better to use dependency injection so you can mock up a do-nothing class (and see that you need to do that in the class constructor) in case of a test plan and point it at that without having to divine all the global state your class depends on.
Related Links:
- Brittleness invoked by Global State and Singletons
- Dependency Injection to Avoid Singletons
- Factories and Singletons
Pattern Use vs Emergence
Patterns are useful as ideas and terms, but unfortunately people seem to feel the need to "use" a pattern when really patterns are implemented as need dictates. Often the singleton specifically is shoehorned in simply because it's a commonly discussed pattern. Design your system with an awareness of patterns, but do not design your system specifically to bend to them just because they exist. They are useful conceptual tools, but just as you don't use every tool in the toolbox just because you can, you shouldn't do the same with patterns. Use them as needed and no more or less.
Example Single-Instance Service Locator
#include <iostream>
#include <assert.h>
class Service {
public:
static Service* Instance(){
return _instance;
}
static Service* Connect(){
assert(_instance == nullptr);
_instance = new Service();
}
virtual ~Service(){}
int GetData() const{
return i;
}
protected:
Service(){}
static Service* _instance;
int i = 0;
};
class ServiceDerived : public Service {
public:
static ServiceDerived* Instance(){
return dynamic_cast<ServiceDerived*>(_instance);
}
static ServiceDerived* Connect(){
assert(_instance == nullptr);
_instance = new ServiceDerived();
}
protected:
ServiceDerived(){i = 10;}
};
Service* Service::_instance = nullptr;
int main() {
//Swap which is Connected to test it out.
Service::Connect();
//ServiceDerived::Connect();
std::cout << Service::Instance()->GetData() << "\n" << ((ServiceDerived::Instance())? ServiceDerived::Instance()->GetData() :-1);
return 0;
}
One word: testing
One of the hallmarks of testability is a loose coupling of classes, allowing you to isolate a single class and test it completely. When one class uses a singleton (and I'm talking about a classic singleton, one that enforces it own singularity thorough a static getInstance() method), the singleton user and the singleton become inextricably coupled together. It is no longer possible to test the user without also testing the singleton.
Singletons are a disaster to test. Since they're static you can't stub them out with a subclass. Since they're global you can't easily change the reference they point to without a recompile or some heavy lifting. Anything that uses the singleton magically gets a global reference to something that's hard to control. This makes it difficult to limit the scope of a test.
The biggest mistakes with Singleton I've seen are you are designing a single-user system (say, a desktop program) and use Singleton for a lot of things (e.g. Settings) and then you want to become multi-user, like a website or a service.
It's similar to what happened to C functions with internal static buffers when they got used in multi-threaded programs.
I would say avoid singletons at all cost. It restricts the scaling of applications. Really analyze the problem your dealing with and think about the scalability and make decisions based on how scalable you want your application.
At the end of the day, a singleton acts as a resource bottleneck if designed incorrectly.
Sometimes you introduce this bottleneck without fully understanding what the implications of doing so will have on your application.
I have come across issues when dealing with multi-threaded applications that are trying to access a singleton resource, but get into deadlocks. This is why I try to avoid a singleton as much as possible.
If you introduce singletons in your design, make sure you understand the runtime implications, do some diagrams and figure out where it could cause an issue.
Singleton is often used as a catch all for stuff that people cannot be bothered to encapsulate properly in the place where it's actually needed, with appropriate accessors.
The end result is a tarball that eventually gathers all static
s in the entire system. How many people here have NEVER seen a class called Globals
in some supposedly OOD code they have had to work with? Ugh.
I wouldn't avoid it completely in any design. However, one must be careful about its usage. It can become the God object in many scenarios and hence defeat the purpose.
Bear in mind, this design pattern is a solution to solve some problems but not all problems. In fact, it is the same for all the design patterns.
I don't consider myself an experienced programmer, but my current opinion is that you actually don't need the Singleton ... yes, it seems easier to work with at first (similarly to globals), but then comes the "oh my" moment when one needs another instance.
You can always pass or inject the instance, I don't really see a situation where it would be significantly easier or necessary to use Singleton
Even if we reject everything, there's still the question of testability of the code
精彩评论