Singleton
From APIDesign
(f - →Singletons are Pathological Liars[http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/]) |
|||
(21 intermediate revisions not shown.) | |||
Line 17: | Line 17: | ||
This page is my attempt to explain this [[paradox]]. It may not be complete yet and certainly needs correction. If you have one, feel free to [[Talk:Singleton|share it]]: | This page is my attempt to explain this [[paradox]]. It may not be complete yet and certainly needs correction. If you have one, feel free to [[Talk:Singleton|share it]]: | ||
- | |||
- | |||
I started by reading the Miško's articles references by Witold. Let's thus use them as headers that will guide us though out this polemic. | I started by reading the Miško's articles references by Witold. Let's thus use them as headers that will guide us though out this polemic. | ||
Line 24: | Line 22: | ||
=== Singletons are Pathological Liars[http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/] === | === Singletons are Pathological Liars[http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/] === | ||
- | It is easy to agree that the sample given | + | It is easy to agree that the sample given in this text shows really horrible style of programming. Nobody who wants to [[Chapter 7|code in modular way]] can have uninitialized objects floating all over the system waiting for some proper call to initialize them: |
<source lang="java"> | <source lang="java"> | ||
Line 43: | Line 41: | ||
* [[Dependency Injection]]'s principle is to always create objects with all their necessary [[environment]] - e.g. it is not possible to create new instance of an object without providing all services it needs. This style almost eliminates [[singleton]]s. | * [[Dependency Injection]]'s principle is to always create objects with all their necessary [[environment]] - e.g. it is not possible to create new instance of an object without providing all services it needs. This style almost eliminates [[singleton]]s. | ||
* [[Injectable Singleton]]s on the other hand solve the ''uninitialized object''s approach by making sure [[singleton]]s are properly initialized as soon as their defining and using classes are linked together. | * [[Injectable Singleton]]s on the other hand solve the ''uninitialized object''s approach by making sure [[singleton]]s are properly initialized as soon as their defining and using classes are linked together. | ||
- | Obviously both of these approaches exorcise the above nightmare of requiring various ''init'' calls to set | + | Obviously both of these approaches exorcise the above nightmare of requiring various ''init'' calls to set your application up. |
=== Where Have All the [[Singleton]]s Gone[http://misko.hevery.com/2008/08/21/where-have-all-the-singletons-gone/] === | === Where Have All the [[Singleton]]s Gone[http://misko.hevery.com/2008/08/21/where-have-all-the-singletons-gone/] === | ||
- | The title is appropriate only in context of [[dependency injection]]. The [[Injectable Singleton|alternative approach]] that we are about to analyze still sticks with its [[singleton]]s. That is why we cannot investigate where did they go - they are still with us. We are just going to verify that several laudable goals are still reached and | + | The title is appropriate only in context of [[dependency injection]]. The [[Injectable Singleton|alternative approach]] that we are about to analyze still sticks with its [[singleton]]s. That is why we cannot investigate where did they go - they are still with us. We are just going to verify that several laudable goals are still reached and state the necessary required conditions. |
- | First of all it is easy to ''don't mix object construction and application logic''. Instead of constructing objects, one | + | First of all it is easy to ''don't mix object construction and application logic''. Instead of constructing objects, one only calls the [[singleton]]'s getter and gets properly initialized instance of the requested interface. Thus one codes just the application logic, the object construction is handled behind the scene by the [[Lookup|infrastructure]]. |
- | It is easy to ''rarely call new operation'' - one does not instantiate services, those are offered via various [[API]] singletons. Enough to get them. However, there is an important limitation: In the server terminology one would say that in such system, there is only one factory: the ''application factory''. There are no ''request factories''. The application factory is determined by the classes loaded into the system. All the classes linked together represent one factory (they can have many different [[singleton]]s, but they are all tight together into one set). If I wanted to have different set of singleton values, I would need to load the classes by different | + | It is easy to ''rarely call new operation'' - one does not instantiate services, those are offered via various [[API]] singletons. Enough to get them. However, there is an important limitation: In the server terminology one would say that in such system, there is only one factory: the ''application factory''. There are no ''request factories''. The application factory is determined by the classes loaded into the system. All the classes linked together represent one factory (they can have many different [[singleton]]s, but they are all tight together into one configuration set). If I wanted to have different set of singleton values, I would need to load the classes by different [[ClassLoader]] again (see [[co-existence]] for more details). |
- | The fact that the [[singleton]] approach supports just ''application factory'' shows the benefits of using true [[dependency injection]] | + | The fact that the [[singleton]] approach supports just ''application factory'' shows the benefits of using true [[dependency injection]]. On the other hand the approach is not inherently limiting. For many applications (especially on desktop) it is fine to have just ''application factories''. There is only one ''help system'', one ''dialog presenter'', one VGA card[http://netbeans.org/projects/platform/lists/dev/archive/2010-01/message/406], etc. Thus this limitation does not violate any [[good]] design practices. Using [[singleton]]s in this way is completely fine. |
=== Root Cause of Singletons[http://misko.hevery.com/2008/08/25/root-cause-of-singletons/] === | === Root Cause of Singletons[http://misko.hevery.com/2008/08/25/root-cause-of-singletons/] === | ||
- | In this piece Miško admits that using ''immutable [[singleton]]'' is OK. Good progress since the first write up. Now the question is what is meant by ''immutable'' | + | In this piece Miško admits that using ''immutable [[singleton]]'' is OK. Good progress since the first write up. Now the question is what is meant by ''immutable''? The content of help system window is certainly not immutable and still it is fine to provide access to its [[API]] via a [[singleton]] under the assumption that it is ready to perform its operation (display some help page). Thus I am going to assume that ''immutable'' [[singleton]] means ''not configurable'' or without a need to call some init code to ''configure'' it. Let's see the necessary attributes of [[injectable singleton]]s to eliminate any need for ''configuration''. |
- | First of all the [[API]] itself needs to provide some implementation of the [[singleton]]. This implementation may be trivial, yet have to be functional. For example a [[singleton]] that is supposed to show dialogs to user and ask questions may always print the question to System.err and choose ''cancel''. This way we eliminate the worry that [[singleton]]s may lie about their dependencies - they don't | + | First of all the [[API]] itself needs to provide some implementation of the [[singleton]]. This implementation may be trivial, yet have to be functional. For example a [[singleton]] that is supposed to show dialogs to user and ask questions may always print the question to System.err and choose ''cancel''. This way we eliminate the worry that [[singleton]]s may lie about their dependencies - they don't have any. As soon as the system successfully links, all [[singleton]]s are ready to work. |
- | Second, the implementations of the [[singleton]]s need to be [[injection|injectable]] - e.g. it is possible to [[injection|inject]] better implementation into the [[singleton]]. Such [[injection]] can happen only before the [[singleton]] is used and (preferably) without invoking any initialization code (see [[Injectable Singleton#Configuration]] for details). This serves two important goals. Production systems are configured to provide ''clever'' (read not ''dummy'') implementations of the [[singleton]]s without affecting code using these [[singleton]]s. | + | Second, the implementations of the [[singleton]]s need to be [[injection|injectable]] - e.g. it is possible to [[injection|inject]] better implementation into the [[singleton]]. Such [[injection]] can happen only before the [[singleton]] is used and (preferably) without invoking any initialization code (see [[Injectable Singleton#Configuration|injectable configuration]] for details). This serves two important goals. Production systems are configured to provide ''clever'' (read not ''dummy'') implementations of the [[singleton]]s without affecting code using these [[singleton]]s. Also, it is possible to easily mock each [[singleton]] in a unit test fully addressing needs of [[Injectable Singleton#Testability|testability]]. |
=== Top 10 things which make your code hard to test[http://misko.hevery.com/2008/07/30/top-10-things-which-make-your-code-hard-to-test/] === | === Top 10 things which make your code hard to test[http://misko.hevery.com/2008/07/30/top-10-things-which-make-your-code-hard-to-test/] === | ||
Line 67: | Line 65: | ||
Let's look how various Miško's advices about testability materialize when using proper [[singleton]]s. | Let's look how various Miško's advices about testability materialize when using proper [[singleton]]s. | ||
- | The test shall be a small instance of the application. How small it can be? As small | + | The test shall be a ''small instance of the application''. How small it can be? As small as the tested part that links together. This means that you can have only [[API]]s you compiled against on your classpath. Nothing else. And yes, this is satisfied (even to greater extent than any [[Dependency Injection|DI]] solution allows): As the [[API]]s offering [[Injectable Singleton]]s have to provide their default ''dummy'' implementation, you are fine to have just them on the execution classpath. Whatever you compiled against is enough to run reasonably well. |
Of course, the default implementations may not be sufficient for testing all aspects of your application. Often you need to test different behavior than the [[singleton]] provides by itself. Here we need to have a ''seam'', a place to change the standard behavior in a testing handy way. But wait! We have a ''seam''! Each [[Injectable Singleton]] is in fact one! | Of course, the default implementations may not be sufficient for testing all aspects of your application. Often you need to test different behavior than the [[singleton]] provides by itself. Here we need to have a ''seam'', a place to change the standard behavior in a testing handy way. But wait! We have a ''seam''! Each [[Injectable Singleton]] is in fact one! | ||
Line 83: | Line 81: | ||
== Conclusions == | == Conclusions == | ||
- | Properly used [[Injectable Singleton|singleton]]s are not that bad as one might think after being massaged by [[dependency injection]] campaigns. As soon as we have [[singleton]]s that are inherently initialized and [[injection|injectable]], we | + | Properly used [[Injectable Singleton|singleton]]s are not that bad as one might think after being massaged by [[dependency injection]] campaigns. As soon as we have [[singleton]]s that are inherently initialized and [[injection|injectable]], we get a solution which is on par with solutions provided by [[dependency injection]] (of course just with ''class level'' [[co-existence]], but that is an [[API]] decision, not an implementation flaw). |
- | Moreover we managed to | + | Moreover we managed to increase [[cluelessness]] of our [[API]] users. Whenever one wants to start using an [[API]] with [[Injectable Singleton]]s, one does not need to spend time writing implementation of any interfaces or properly configure system to provide them. Trivial implementations are inherently present in the [[Injectable Singleton]]s by their own. Of course, if one is not satisfied by them, one is allowed to provide better ones. In some way the [[Injectable Singleton]]s bring the [[Component Injection]] much closer to the motto of [[Convention over Configuration]]: ''If you don't care, use the defaults. If that is not enough for you, specify an alternative''! |
With ten years of active use, seamless [[testability]] (via ''seams''), adherence to [[Convention over Configuration|simplicity of use]] and also with possible bridges with modern [[Dependency Injection]] technologies (see [[LookupAndSpring]]), I would not dare to call [[singleton]]s old trash. Try [[Injectable Singleton]]s, you'll find them friendly! | With ten years of active use, seamless [[testability]] (via ''seams''), adherence to [[Convention over Configuration|simplicity of use]] and also with possible bridges with modern [[Dependency Injection]] technologies (see [[LookupAndSpring]]), I would not dare to call [[singleton]]s old trash. Try [[Injectable Singleton]]s, you'll find them friendly! | ||
[[Category:APIDesignPatterns]] | [[Category:APIDesignPatterns]] |
Current revision
Among other things the Chapter 7 also introduces the NetBeans pattern for doing Component Injection. The claims there are not fully aligned with common know-how of developers that use Dependency Injection. The most surprising thing is that NetBeans APIs commonly contain singletons (and yet there are no problems or design issues and testability is supported). It is quite common to see static methods like:
public abstract class WindowSystem { public static WindowSystem getDefault() { /* some impl */ } }
This is something a Dependency Injection fan would have never done. Recently Witold Szczerba shared a very interesting observation on our mailing list[1]:
Now I feel totally lost. Jaroslav Tulach and Miško Hevery ... proclaim totally contradictory theories about the bases for building applications. Or they not? I am still trying to figure it out. In Chapter 7: "Use modular architecture", Jaroslav describes the concept of generic registry, which is #2 on a list of "Top 10 things which make your code hard to test"[2]... Do we have two mutually exclusive yet both correct theories?
This page is my attempt to explain this paradox. It may not be complete yet and certainly needs correction. If you have one, feel free to share it:
I started by reading the Miško's articles references by Witold. Let's thus use them as headers that will guide us though out this polemic.
Contents |
Singletons are Pathological Liars[3]
It is easy to agree that the sample given in this text shows really horrible style of programming. Nobody who wants to code in modular way can have uninitialized objects floating all over the system waiting for some proper call to initialize them:
Database.init(); OfflineQueue.init(); CreditCardProcessor.init(); CreditCard c = new CreditCard( "1234 5678 9012 3456", 5, 2008 ); c.charge(100);
The fact that in order to charge a credit card one needs to initialize various subsystems needs to be made more visible otherwise proper use and maintenance of such system requires non-trivial amount of knowledge and goes directly against the principle of cluelessness.
Miško's philippic is targeted against singletons, however I feel the problem is rather in existence and usage of uninitialized objects. Yes, it is easier to let uninitialized singletons escape to foreign code, however this can be simulated with regular objects as well. As such I am going to slightly shift focus and complain about uninitialized objects, potentially finding ways to use singletons so they are properly initialized.
As an executive overview, let me say that there seem to be two approaches to prevent uninitialized objects:
- Dependency Injection's principle is to always create objects with all their necessary environment - e.g. it is not possible to create new instance of an object without providing all services it needs. This style almost eliminates singletons.
- Injectable Singletons on the other hand solve the uninitialized objects approach by making sure singletons are properly initialized as soon as their defining and using classes are linked together.
Obviously both of these approaches exorcise the above nightmare of requiring various init calls to set your application up.
Where Have All the Singletons Gone[4]
The title is appropriate only in context of dependency injection. The alternative approach that we are about to analyze still sticks with its singletons. That is why we cannot investigate where did they go - they are still with us. We are just going to verify that several laudable goals are still reached and state the necessary required conditions.
First of all it is easy to don't mix object construction and application logic. Instead of constructing objects, one only calls the singleton's getter and gets properly initialized instance of the requested interface. Thus one codes just the application logic, the object construction is handled behind the scene by the infrastructure.
It is easy to rarely call new operation - one does not instantiate services, those are offered via various API singletons. Enough to get them. However, there is an important limitation: In the server terminology one would say that in such system, there is only one factory: the application factory. There are no request factories. The application factory is determined by the classes loaded into the system. All the classes linked together represent one factory (they can have many different singletons, but they are all tight together into one configuration set). If I wanted to have different set of singleton values, I would need to load the classes by different ClassLoader again (see co-existence for more details).
The fact that the singleton approach supports just application factory shows the benefits of using true dependency injection. On the other hand the approach is not inherently limiting. For many applications (especially on desktop) it is fine to have just application factories. There is only one help system, one dialog presenter, one VGA card[5], etc. Thus this limitation does not violate any good design practices. Using singletons in this way is completely fine.
Root Cause of Singletons[6]
In this piece Miško admits that using immutable singleton is OK. Good progress since the first write up. Now the question is what is meant by immutable? The content of help system window is certainly not immutable and still it is fine to provide access to its API via a singleton under the assumption that it is ready to perform its operation (display some help page). Thus I am going to assume that immutable singleton means not configurable or without a need to call some init code to configure it. Let's see the necessary attributes of injectable singletons to eliminate any need for configuration.
First of all the API itself needs to provide some implementation of the singleton. This implementation may be trivial, yet have to be functional. For example a singleton that is supposed to show dialogs to user and ask questions may always print the question to System.err and choose cancel. This way we eliminate the worry that singletons may lie about their dependencies - they don't have any. As soon as the system successfully links, all singletons are ready to work.
Second, the implementations of the singletons need to be injectable - e.g. it is possible to inject better implementation into the singleton. Such injection can happen only before the singleton is used and (preferably) without invoking any initialization code (see injectable configuration for details). This serves two important goals. Production systems are configured to provide clever (read not dummy) implementations of the singletons without affecting code using these singletons. Also, it is possible to easily mock each singleton in a unit test fully addressing needs of testability.
Top 10 things which make your code hard to test[7]
Let's look how various Miško's advices about testability materialize when using proper singletons.
The test shall be a small instance of the application. How small it can be? As small as the tested part that links together. This means that you can have only APIs you compiled against on your classpath. Nothing else. And yes, this is satisfied (even to greater extent than any DI solution allows): As the APIs offering Injectable Singletons have to provide their default dummy implementation, you are fine to have just them on the execution classpath. Whatever you compiled against is enough to run reasonably well.
Of course, the default implementations may not be sufficient for testing all aspects of your application. Often you need to test different behavior than the singleton provides by itself. Here we need to have a seam, a place to change the standard behavior in a testing handy way. But wait! We have a seam! Each Injectable Singleton is in fact one!
Applications with good testability are said to have a lot of seams. One could even try to argue that the more seams your application have, the more testable it will be. Do you know what that means when using Injectable Singletons? The more singletons, the better!
Conclusions
Properly used singletons are not that bad as one might think after being massaged by dependency injection campaigns. As soon as we have singletons that are inherently initialized and injectable, we get a solution which is on par with solutions provided by dependency injection (of course just with class level co-existence, but that is an API decision, not an implementation flaw).
Moreover we managed to increase cluelessness of our API users. Whenever one wants to start using an API with Injectable Singletons, one does not need to spend time writing implementation of any interfaces or properly configure system to provide them. Trivial implementations are inherently present in the Injectable Singletons by their own. Of course, if one is not satisfied by them, one is allowed to provide better ones. In some way the Injectable Singletons bring the Component Injection much closer to the motto of Convention over Configuration: If you don't care, use the defaults. If that is not enough for you, specify an alternative!
With ten years of active use, seamless testability (via seams), adherence to simplicity of use and also with possible bridges with modern Dependency Injection technologies (see LookupAndSpring), I would not dare to call singletons old trash. Try Injectable Singletons, you'll find them friendly!