Testing Blog
Where Have All the Singletons Gone?
Thursday, August 21, 2008
by
Miško Hevery
In
Singletons are Pathological Liars
we discussed the problems of having singletons in your code. Let's build on that and answer the question "If I don't have singletons how do I ensure there is only one instance of X and how do I get X to all of the places it is needed?"
An OO application is a graph of objects. There are three different kinds of graphs I think of when I design an application
Collaborator Graph
: T
his is the
graph of objects that would be emitted if you serialized your application. This shows which objects are aware of which others. (through object's fields)
Construction Graph
: This graph shows which object created which other ones.
Call Graph
: This graph shows
which other methods each method calls
. A stack-trace would be a single slice through this graph.
If the new operators are mixed with application logic (see:
How to Think About the new Operator
) then the Constructor Graph and the Collaborator Graph tend to be one and the same. However, in an application which uses Dependency Injection the two graphs are completely independent. Lets have a closer look at our CreditCardProcessor example. Suppose this is our collaborator graph which we need to execute a request.
The above shows the application collaborator graph. The letter (S/R) in the corner designates object lifetime; either Singleton or Request scope. Now, just to be clear, there is nothing wrong with having a single instance of a class. The problem arises only when the singleton is available through a global "instance" variable as in Singleton.getInstance().
The HTTP request would come to AuthenticatorPage which would collaborate with Authenticator to make sure the user is valid and forward a valid request onto ChargePage which would then try to load the user from UserRepository and create the credit card transaction which would be processed by CrediCardProcessor. This in turn would collaborate with OfflineQueue to get the work done.
Now, in order to have a testable codebase we have to make sure that we don't mix the object construction with application logic. So all of the above objects should rarely call the new operator (value objects are OK). Instead each of the objects above would declare its collaborators in the constructor. AuthenticatorPage would ask for ChargePage and Authenticator. ChargePage would ask for CreditCardProcessor and UserRepository. And so on. We have moved the problem of construction elsewhere.
In our tests it is now easy to instantiate the graph of objects and substitute test-doubles for our collaborators. For example if we would like to test the AuthenticatorPage, we would instantiate a real AuthenticatorPage with mock ChargePage and mock Authenticator. We would than assert that a request which comes in causes appropriate calls on Authenticator and ChargePage only if authentication is successful. If the AuthenticatorPage were to get a reference to Authenticator from global state or by constructing it, we would not be able to replace the Authenticator with a test-double. (This is why it is so important not to mix object construction with application logic. In the unit-test what you instantiate is a sub-set of the whole application. Hence the instantiation logic has to be separate from application logic! Otherwise, it's a non-starter.)
So now the problem is, how do we construct the graph of objects?
In short we move all of the new operators to a factory. We group all of the objects of similar lifetime into a single factory. In our case all of the singletons end up in ApplicationFactory and all of the Pages end up in RequestFactory. The main method of our application instantiates an ApplicationFactory. When we call build() the ApplicationFactory in turn instantiates its list of objects (Database, OfflineQueue, Authenticator, UserRepository, CreditCardProcessor and RequestFactory). Because each of the objects declares its dependency, the ApplicationFactory is forced to instantiate the objects in the right order. In our case it must instantiate the Database first and than pass the reference to UserRepository and OfflineQueue. (The code will simply not compile any other way.)
Notice that when we create a RequestFactory we must pass in references to the Authenticator, UserRepository and CreditCardProcessor. This is because when we call build() on RequestFactory it will try to instantiate AuthenticatorPage which needs the Authenticator. So we need to pass the Authenticator into the constructor of RequestFactory and so on.
At run-time an HTTP request comes in. The servlet has a reference to RequestFactory and calls build(). The servlet now has a reference to the AuthenticatorPage and it can dispatch the request for processing.
Important things to notice:
Every object only has references to what it needs directly! No passing around of objects which are not directly needed by the code. There is no global state at all. Dependencies are obvious since each object only asks for what it needs.
If an object needs a reference to a new dependency it simply declares it. This change only affects the corresponding factory, and as a result, it is very isolated.
All of the new operators end up in the factories; application logic is devoid of new operators.
You group all of the objects with the same lifetime into a single factory (If the factory gets too big you can break it up into more classes, but you can still think of it as a single factory)
The problem of "how do I ensure that I only have one of something" is nicely sidestepped. You instantiate only a single ApplicationFactory in your main, and as a result, you only instantiate a single instance of all of your singletons.
Now the factories become largely a series of object creations
. Totally boring stuff, so boring a computer could generate the code. Simply look at the constructor and recursively instantiate whatever the constructor wants. Wait, a computer can generate it! Its called
PicoContainer
or
GUICE
! So you don't actually have to write the factories.
No comments :
Post a Comment
Labels
Aaron Jacobs
1
Adam Porter
1
Alan Faulkner
1
Alan Myrvold
1
Alberto Savoia
4
Alek Icev
2
Alex Eagle
1
Allen Hutchison
6
Andrew Trenk
8
Android
1
Anthony Vallone
25
Antoine Picard
1
APIs
2
App Engine
1
April Fools
2
Arif Sukoco
1
Bruce Leban
1
C++
11
Chaitali Narla
2
Christopher Semturs
1
Chrome
3
Chrome OS
2
Dave Chen
1
Diego Salas
2
Dmitry Vyukov
1
Dori Reuveni
1
Eduardo Bravo Ortiz
1
Ekaterina Kamenskaya
1
Erik Kuefler
3
Espresso
1
George Pirocanac
2
Google+
1
Goranka Bjedov
1
GTAC
54
Hank Duan
1
Harry Robinson
5
Havard Rast Blok
1
Hongfei Ding
1
James Whittaker
42
Jason Arbon
2
Jason Elbaum
1
Jason Huggins
1
Java
5
JavaScript
7
Jay Han
1
Jessica Tomechak
1
Jim Reardon
1
Jobs
14
Joe Allan Muharsky
1
Joel Hynoski
1
John Penix
1
John Thomas
3
Jonathan Rockway
1
Jonathan Velasquez
1
Julian Harty
5
Julie Ralph
1
Karin Lundberg
1
Kaue Silveira
1
Kevin Graney
1
Kirkland
1
Kurt Alfred Kluever
1
Lesley Katzen
1
Marc Kaplan
3
Mark Ivey
1
Mark Striebeck
1
Marko Ivanković
1
Markus Clermont
3
Michael Bachman
1
Michael Klepikov
1
Mike Wacker
1
Misko Hevery
32
Mobile
2
Mona El Mahdy
1
Noel Yap
1
Patricia Legaspi
1
Patrick Copeland
23
Patrik Höglund
5
Peter Arrenbrecht
1
Phil Rollet
1
Philip Zembrod
4
Pooja Gupta
1
Radoslav Vasilev
1
Rajat Dewan
1
Rajat Jain
1
Rich Martin
1
Richard Bustamante
1
Roshan Sembacuttiaratchy
1
Ruslan Khamitov
1
Sean Jordan
1
Sharon Zhou
1
Shyam Seshadri
4
Simon Stewart
2
Stephen Ng
1
Tejas Shah
1
Test Analytics
1
Tony Voellm
2
TotT
54
Vojta Jína
1
WebRTC
2
Yvette Nameth
2
Zhanyong Wan
6
Zuri Kemp
2
Archive
2015
December
November
October
August
June
May
April
March
February
January
2014
December
November
October
September
August
July
June
May
April
March
February
January
2013
December
November
October
August
July
June
May
April
March
January
2012
December
November
October
September
August
2011
November
October
September
August
July
June
May
April
March
February
January
2010
December
November
October
September
August
July
June
May
April
March
February
January
2009
December
November
October
September
August
July
June
May
April
February
January
2008
December
November
October
September
August
Taming the Beast (a.k.a. how to test AJAX applicat...
Root Cause of Singletons
TotT: Sleeping != Synchronization
Where Have All the Singletons Gone?
Singletons are Pathological Liars
TotT: 100 and counting
TotT: A Matter of Black and White
Writing Testable Code
GTAC Attendance Application deadline Aug15
July
June
May
April
March
February
January
2007
October
September
August
July
June
May
April
March
February
January
Feed
Follow @googletesting
No comments :
Post a Comment