Reputation: 9788
Very often singletons are a bad thing (e.g. see here and here) and I want to avoid them. A typical use case for singletons I don't have an alternative for yet is when you have multiple objects which have to reference some sort of parent/global object.
Example 1: you have some sort of a tree and every leaf-object has to know the root object. (which is bad design but it's just for the example)
Example 2: you have a 'global' configuration object typically accessed like global_config::get_instance()->get_item(name)
Question: for the given examples - are there any alternative concepts to singletons apart from extensive dependency passing and per-instance-storing (aka Dependancy Injection
)?
Upvotes: 2
Views: 982
Reputation: 12912
Question: for the given examples - are there any alternative concepts to singletons apart from extensive dependency passing and per-instance-storing (aka
Dependency Injection
)?
In both cases, you're talking about a visibility requirement of the information, not a singleton (i.e. only one instance of an object) requirement.
Craig Larman's "Applying UML and Patterns" states there are four ways that an object A can access an object B:
Attribute visibility -- B is an attribute of A.
Parameter visibility -- B is a parameter of a method of A.
Local visibility -- B is a (non-parameter) local object in a method of A.
Global visibility -- B is in some way globally visible.
The last one is what is provided by a Singleton. It's why this pattern is controversial.
Dependency injection is the first option -- B is injected to A in its constructor.
The general rule of thumb: the more you expose a piece of information in your design, the harder it could be to change your design later (because of coupling and dependency on that information). Global visibility makes it easy in the short term to solve some access problems in your design. It has the risk of creating problems in the long-term, because of over-exposure of information.
Upvotes: 1
Reputation: 9997
One approach that I sometimes find useful is to allow the get_instance()
method to receive some parameter and return different objects depending on this parameter, something like this: global_config::get_instance(string serviceName)
. If you have several singleton classes, you can use that same parameter to obtain each of them, so each user will have to know only one parameter, not keep references to all singletons.
For a start, all calls to get_instance()
can return the same object disregarding the argument, but if the development requires you to have different singletons for different parts of application, you can adapt your code by having that different parts of application pass different parameters to get_instance()
.
Moreover, this will also solve the problem of testing of your application, as you can find some way to pass a special mock parameter to get_instance()
to receive mock object.
This may be useful for your global_cofig
example, but for your tree example you should definitely keep a reference to the root in the leafs.
Upvotes: 1
Reputation: 14392
Singletons only solve one thing: the "static initialization order fiasco" where global objects are instantiated in an undefined order so they can't be dependent on each other.
Otherwise, singletons are just globally accessible objects which just happen to only exist just once.
So for containers, like the tree in the first example, a global instance is not worse than a singleton. Also, a configuration (example 2) is mostly a container of settings deserialized at startup, so nothing stops you from making it a normal global instance.
A little better design is to group globals for an application in an "application" class, so it is at least clear that they are application wide objects and the configuration should be filled in at startup anyway.
Even better (for bigger applications) is to make an application out of module like bigger objects and pass the needed "globals" to them from the "application" class to have a clearer dependency view. These modules can also contain bigger objects that receive references to the "globals", but this doesn't have to go to each object as long as they know how to access their modular globals.
One final argument for choosing a more dependency injection approach is that your objects will be easier to do module tests on, because you can inject test objects more easily and all globals with state tend to interfere with a series of tests.
Upvotes: 2