Singleton - Programming in the Large with Design Patterns (2012)

Programming in the Large with Design Patterns (2012)

Chapter 2. Singleton

Using the Singleton design pattern to avoid introducing a global variable is like trying to eat healthy at McDonald’s by ordering a Big Mac without the bun. Yes, doing so will reduce the calorie count somewhat, but it does little to mitigate the real problem with the meal.

Introduction

The Singleton design pattern is simple in concept and easy to apply. The typical implementation requires adding just 5-6 statements to an existing class.

The context for the Singleton design pattern is the situation where an application needs a single instance of a given class and a global point of access to it. The classic example is logging.

For the purposes of illustration, assume you have a simple logging class with methods for recording information on different types of events:

public class SimpleLog {

public void info(String message);

public void error(String message);

}

Only one instance of the class is needed and this instance has potential use throughout the program.

One design option is to declare an instance of the class as a global variable and establish conventions for creating and using the global variable.

SimpleLog logger;

Declaring a global variable would provide convenient access from all points in the program but conventions for access aren’t as reliable as language mechanisms that enforce access rules.

A slightly better solution is to use the Singleton design pattern. “Slightly” is the operative word here. The Singleton design pattern has some advantages over simply declaring a global variable, but it doesn’t completely eliminate all the problems associated with the use of global variables.

When faced with the two criteria for using the Singleton design pattern (single instance and global access) oftentimes the best approach is to first look for ways of restructuring the application such that global access isn’t needed. Only after exhausting all reasonable attempts to limit the scope of the class instance, should the Singleton design pattern be used.

The tradeoffs between global variables and singletons are described in more detail in the discussion section below.

Intent

The Singleton design pattern ensures that not more than one instance of a class is created and provides a global point of access to this instance.

Solution

To make a class a singleton:

1. Make the constructor of the class private to prevent clients from creating instances of the class directly.

2. Add a public static method getInstance() to the class that returns an instance of the class. The first time getInstance() is called an instance of the class is created, cached and returned. On subsequent calls, the cached instance is returned.

Visually, the design of a singleton class looks something like:

Figure 19 UML diagram for Singleton design pattern

In a multithreaded application special care is needed to ensure that getInstance() is thread-safe. The sample implementation shown in the UML comment box above is thread-safe. The keyword synchronized on the static method getInstance() means that when one thread is executing inside the method, all other threads that attempt to enter getInstance() will block. If the method wasn't synchronized, it is possible that two threads could check the value of the class variable instance at the same time, find it null and both attempt to create an instance of the class Singleton.

One drawback with the implementation above is performance. Synchronized methods are much slower than non-synchronized ones. In practice though, unless getInstance() is called repeatedly, the overhead will be negligible. If performance is an issue, there are three alternate implementations to consider.

First, if the class doesn’t need to support multiple threads of control, the simplest way of improving performance is to simply take off the key word synchronized.

Second, if the overhead of creating the instance of the class is small and/or the instance will always be created, consider creating the instance at startup:

The Java runtime guarantees static class variables are initialized before they are accessible from any thread. One limitation of this solution is that any data needed to initialize the instance of the singleton must be available during static initialization.

And finally, the double-checked locking synchronization technique can be used to speed up the common case. (Those coding in Java should know that because of a bug in the JVM, double-check locking is not guaranteed to work in Java 1.4 or earlier.)

Double-checked locking is slightly more complex than using a synchronized method but is more efficient because once the object is create, requests for the object aren’t synchronized.

Sample Code

The following code shows how to implement a simple logging class as a singleton.

Figure 20 A simple logging class implemented as a singleton

Clients of the singleton use the static class method getInstance() to access the shared object:

public class Client {

public void f() {

SimpleLog logService = Simplelog.getInstance();

logService.debug(“Starting Client::f()”);

. . .

}

}

Figure 21 Sample client code for accessing a singleton

Discussion

The Singleton design pattern has the distinction of being not only one of the simplest design patterns, but also ironically one of the most controversial. Much of the controversy stems from a failure to fully appreciate the consequences of using the pattern. The principle problem with the pattern is that it introduces global state into a program. Programmers that don’t recognize this may unwittingly use the pattern in situations where there are better design alternatives.

To understand the potential negative consequences associated with improper use of the Singleton design pattern, consider two designs for a class that encapsulates an integer.

Figure 22 Comparing use of a global variable to use of a singleton

The design on the left declares a global variable of type S. The design on the right implements class S as a singleton. Most programmers, even if they don’t fully understand all the reasons why, know to avoid global variables whenever possible. So, is the design on the right significantly better than the design on left because it avoids the use of a global variable? The short answer is no. While the design on the right avoids the use of a global variable per se, it still introduces global state, and it’s the consequences of global state that makes global variables so unpopular.

Using the Singleton design pattern to avoid introducing a global variable is like trying to eat healthy at McDonald’s by ordering a Big Mac without the bun. Yes, doing so will reduce the calorie count somewhat, but it does little to mitigate the real problem with the meal.

Figure 23 compares use of a singleton class to the declaration of a global variable in terms of benefits and liabilities. As you can see, a singleton class has more benefits and fewer liabilities than a global variable. However, not all liabilities are created equal (i.e. have equal consequence). The most severe liabilities are those common to both global variables and singleton classes. Consequently, declaring a class as a singleton is only marginally better than declaring a global variable.

Figure 23 Benefits and liabilities of singletons and global variables

One of the main drawbacks with declaring a class as a singleton (and using global variables as well) is that it can introduce hidden dependencies that weaken modularity. Figure 24 shows an example of how shared access to a singleton can create implicit coupling between two modules.

Figure 24 Implicit coupling caused by shared access to a singleton

Module A is tightly coupled to module B but you wouldn’t know it by looking at just the interfaces of the two modules (or a class diagram of both modules). The coupling is created indirectly through shared access to singleton S.

In a well-modularized system you can study or test one module independent of others. Ideally, in order to study or test module A in the example above, you shouldn’t have to look beyond the borders of module A. However, because module A accesses global state in singleton S, you also have to be aware of code in other modules that update this global state. If you want to understand method f() in module A you also have to inspect the code in method h() in module B because h() modifies global state accessed by f(). Notice that the dependency goes beyond the interface to module B. You can’t simply study the interface of h() because global state is updated as a side-effect of calling h(). You have to study the detailed implementation of h().

Weakened modularity also complicates testing. In the following code fragment, the class PaymentService is implemented as a singleton. This design makes it hard to test the routine checkout() in isolation. Any tests ran on the routine checkout() are going to affect whatever instance ofPaymentService is available. Running a test on checkout() is likely to leave the system in a different state. This can cause havoc with test case ordering, repeatability, debugging, etc.

Given the magnitude of the liabilities associated with the Singleton design pattern, you should approach the declaration of a singleton as you would the declaration of a global variable and exhaust all other design alternatives before settling for a singleton. The principle way of avoiding a singleton class is to restructure the code to limit the scope of the object in question. If the object doesn’t have to be visible to the whole program, it can be declared local to some class or routine and passed as a parameter to the routines that do need access.

If you do choose to use a singleton in order to avoid passing a frequently used variable from routine to routine, consider making the singleton semi-global. Many languages have support for limiting the visibility of a class. For example, in Java declaring a class public makes it visible to the whole program. Declaring a class without an access modifier limits visibility to the package in which it is declared. Remember, the issue isn’t that global scope is bad, but rather, that scope should be as narrow as possible. A class variable in a 300-line class is more of a problem than a global variable in a 200-line program.

Something else to consider when balancing the convenience of global access with the advantages of limited scope is how the class will be used. Read-only (immutable) or write-only classes are less of a problem than read/write classes. Classes with read-only data are like global constants. Classes such as loggers that are write-only have no effect on the program’s execution. In both cases, what is going on in one section of code has no influence on another section of code. This greatly mitigates the issues with global data.

Related Patterns

The Singleton design pattern can be used with the Abstract Factory design pattern to guarantee at most one factory class is created. It can also be used with the State design pattern in order to avoid recreating state classes when the state switches.