Singletons The Safe Way
The Singleton pattern is a useful pattern in Android development, and Java development at large. The Singleton pattern is defined as follows:
In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
Why might we aim to limit the instances of a Class to just a single instance?
Consider your application’s database, image cache, network client, etc. Our apps are chock full of artifacts where it only makes sense to have one. What would it mean if you had a 2nd or 3rd database? It probably means someone made a mistake. The Singleton pattern helps us limit certain artifacts to a single instance.
Implementing Singletons
To implement a Singleton in Java, use a Class’ static access to create and offer a single instance of the Class:
public class MyThing { private static MyThing sInstance; public static MyThing getInstance() {
// Notice that construction can only happen once.
if (null == sInstance) {
sInstance = new MyThing();
} return sInstance;
} private MyThing() {
// Private constructor prevents multiple instances.
}}
With the above implementation, the following is how clients use the Singleton Object:
MyThing myThing = MyThing.getInstance();
myThing.doStuff();
That’s the Singleton pattern.
The Danger of Singletons
Can something as simple as a Singleton pose a threat to code health? Absolutely.
The Unit Test State Danger
Consider the following Class:
public class PatientScheduler { public boolean scheduleAppointment(Date date) {
PatientCalendar patientCalendar = PatientCalendar.getInstance();
if (patientCalendar.hasAvailability(date)) {
// schedule appointment
return true;
} else {
return false;
}
}}
Also, consider the following unit test:
public class PatientSchedulerTest { public void testAppointmentScheduledWhenDateIsClear() {
PatientScheduler patientScheduler = new PatientScheduler();
boolean scheduled = patientScheduler.scheduleAppointment(mDate);
assertTrue(scheduled);
}}
What can go wrong with this unit test?
PatientScheduler uses the PatientCalendar Singleton. Singletons are held statically within their Class definition. Static elements live as long as their containing Process. Unit tests run within a single Process.
Thus, the same instance of PatientCalendar would exist across all unit tests in PatientSchedulerTest. If we only run the single test above then things will be alright. But if we run that same test 2 or more times within the same test suite, every test but the first test will fail. Why?
The first time PatientCalendar is accessed it is created fresh. Then our unit test schedules an appointment for a given date. Then our test finishes. But when the next test runs, the same scheduled appointment still exists. If we scheduled an appointment for May 5th at 2pm, that appointment will exist across all of our unit tests. If another test expects May 5th at 2pm to be available then that test will fail. And it will be really difficult for a developer to understand why these tests “randomly” fail.
Thus, one of the downsides of Singletons is that within test suites a Singleton maintains its state across test runs which often breaks tests in confusing ways.
The Dependency Danger
Recall the PatientScheduler Class from the previous section. Imagine that you looked up the API for PatientScheduler and you noticed the following two declarations:
// A no-argument constructor
public PatientScheduler();// A method for scheduling appointments.
public boolean scheduleAppointment(Date date);
It would appear that PatientScheduler has no external dependencies so you can create one and call scheduleAppointment(date) without worry. Of course, this begs the question of how scheduleAppointment(date) even works if we assume no dependencies, but you’re in a hurry to ship something so you take the API at face value.
After implementing the desired behavior, you start to write a unit test for scheduling:
public class PatientSchedulerTest { public void testAppointmentNotScheduledWhenDateIsBooked() {
// Wait...how do I mock the patient's calendar? PatientScheduler patientScheduler = new PatientScheduler();
boolean scheduled = patientScheduler.scheduleAppointment(mDate);
assertFalse(scheduled);
}}
Suddenly you realize there was a dependency all along. For this unit test you need to mock a fake patient calendar that says its booked for a given date. But how do you get your mock patient calendar into PatientScheduler?
You jump back to the JavaDocs for PatientScheduler only to realize there isn’t a single method for setting a PatientCalendar. What is going on?
Now you’re in the source code for PatientScheduler reading each line in horror! The author of PatientScheduler used a damn Singleton! Now you’re hosed…
Singleton’s cannot be mocked because their static accessors cannot be overridden. It is now impossible for you to effectively test against PatientScheduler. Unless you are allowed to change the implementation of PatientScheduler to explicitly receive a dependency, you’re going to have to hope and pray that the code on top of PatientScheduler is correct.
But wait…theres’ more!
A PatientCalendar might be a lightweight Object that can easily run during a unit test without creating a headache. But remember the other examples of Singleton use-cases? What about your database? What if PatientScheduler looked like this?
public class PatientScheduler { public boolean scheduleAppointment(Date date) {
Cursor appointments = Database.getInstance()
.getAppointments(date)
.execute(); if (appointments.isEmpty()) {
// schedule appointment
return true;
} else {
return false;
}
}}
Uh-oh. Now this nightmare is getting worse. Not only does this new implementation prevent mocking, but now your unit tests require a running database to execute! This is probably about the time that your team decides that unit tests are overrated and you vote to stop writing them.
Fundamentally the mocking problem is one of undeclared dependencies. PatientScheduler has a dependency on another system but fails to ever declare that dependency, let alone give developers an opportunity to switch it out.
Practicing Safe Singletons
The solution to the dangers of Singletons is as simple as the pattern itself.
Only access Singletons from top-level control structures.
What is a top-level control structure? It depends on your system. In Android top-level control structures include:
- The Application Class
- Activitys
- Fragments
- Services
- BroadcastReceivers
Why are these artifacts considered top-level control structures? Because each of these artifacts is generated by the Android operating system. For example, there is nothing “above” an Activity that can configure an Activity instance. Instead, the Android OS creates and destroys Activity instances as needed. Thus, an Activity is the “top” of a given control structure.
How can we fix our PatientScheduler woes? Like this:
public class PatientScheduler { private PatientCalendar mPatientCalendar; public PatientScheduler(PatientCalendar calendar) {
// PatientCalendar is an explicit dependency.
mPatientCalendar = calendar;
} public boolean scheduleAppointment(Date date) {
if (mPatientCalendar.hasAvailability(date)) {
// schedule appointment
return true;
} else {
return false;
}
}}
Then, in an Activity we might utilize a PatientScheduler like this:
public class MyActivity extends Activity { public void schedulePatientAppointment(Date date) {
// Get an instance of PatientCalendar from Singleton
PatientCalendar calendar = PatientCalendar.getInstance(); // Do the scheduling.
PatientScheduler scheduler = new PatientScheduler(calendar);
boolean scheduled = scheduler.scheduleAppointment(date); // Handle success or failure of scheduler.
}}
Finally, unit tests are easily written with mock data:
public class PatientSchedulerTest { public void testAppointmentNotScheduledWhenDateIsBooked() {
// Mock a calendar
PatientCalendar bookedCal = createAlwaysBookedCalendar(); PatientScheduler scheduler = new PatientScheduler(bookedCal);
boolean scheduled = patientScheduler.scheduleAppointment(mDate);
assertFalse(scheduled);
}}
Problem solved.
If you only ever access Singletons from Applications, Activitys, Fragments, Services, and BroadcastReceivers then you will always be able to write unit tests for your Classes, your dependencies will always be explicit, and your overall encapsulation boundaries will improve.
But what about testing those top-level control structures?
Its true that the Singleton version of dependencies are still baked into your top-level control structures, but you can’t unit test those Classes anyway. An Activity, by definition, is an integration system. Its job is to integrate your UI with your business logic within the operating system. Any test of a top-level control structure is an integration test. Therefore, testing against actual Singleton implementations should be acceptable for top-level control structures.
Have fun with Singletons, but be safe.