Monday, March 24, 2008

Working With Design Patterns: Singleton

Source


By Jeff Langr

Go to page: 1 2 Next

The singleton is perhaps the most maligned software design pattern out there. It's right up there with the visitor pattern, which is often denigrated by developers as too complex. Yet, the notion behind singleton is simple: ensure that only one instance of a specific type exists during application execution.

The default Java behavior, as with most languages, allows you to create as many instances of a given class as you want. Up until J2SE 5, Java allowed no direct way to impose a restriction on the number of instances. In J2SE 5 and later versions, you can use the enum construct to constrain how many objects of a given type exist. You can also provide a unique name for each of these known instances.

In languages such as C and C++, an enum ("enumeration") is simply a series of integral values, where each element in the series maps to a unique symbol. The use of enums allows you to craft more expressive code. In Java, the enum is better viewed as a class like any other, except for a few key differences. An enum can have constructors, methods, and fields, but it cannot be subclassed. An enum definition first supplies the names of the known instances; no other instances can be created by any means.

From a simplistic standpoint, you could consider the catalog in a library system a candidate for a singleton. After all, you don't want to have to know what books were added to what catalog. Listing 1 shows how you might implement the catalog singleton using the enum construct.

Listing 1: The catalog singleton, expressed as an enum.

import java.util.*;

public enum Catalog {
soleInstance;

private Map books = new HashMap();

public void add(Book book) {
books.put(book.getClassification(), book);
}

public Book get(String classification) {
return books.get(classification);
}
}

Not much to it, is there? Client code must refer to the single known object, soleInstance (see Listing 2).

Listing 2: Accessing the enum singleton.

import static org.junit.Assert.*;
import org.junit.*;

public class CatalogTest {
@Test
public void singleBook() {
Book book = new Book("QA123", "Schmidt", "Bugs", "2005");
Catalog.soleInstance.add(book);
assertSame(book, Catalog.soleInstance.get("QA123"));
}
}

If you attempt to directly instantiate another Catalog object:

   new Catalog();    // this won't compile!

...your code will not even compile, as the comment states.

Is this really a design pattern? Well, yes, it's an implementation of a design pattern, one that the Java language makes very simple.

Most developers are probably more familiar with the concept of "rolling their own" singleton. In Java, this means supplying a static-side creation method and making the constructor private. See Listing 3.

Listing 3: Hand-grown catalog singleton.

import java.util.*;

public class Catalog {
private static final Catalog soleInstance = new Catalog();
private Map books = new HashMap();

public static Catalog soleInstance() {
return soleInstance;
}

private Catalog() {
// enforce singularity
}

public void add(Book book) {
books.put(book.getClassification(), book);
}

public Book get(String classification) {
return books.get(classification);
}
}

The private constructor ensures that no other clients can create a Catalog object. From the client standpoint, things don't look very different (see Listing 4).

Listing 4: Accessing the hand-grown singleton.

import static org.junit.Assert.*;
import org.junit.*;

public class CatalogTest {
@Test
public void singleBook() {
Book book = new Book("QA123", "Schmidt", "Bugs", "2005");
Catalog.soleInstance().add(book);
assertSame(book, Catalog.soleInstance().get("QA123"));
}
}

Seems like a simple, harmless pattern. So what's all the fuss?

Well, a single is simply object-oriented speak for a global variable. The software development community has long known about problems that global variables introduce. Primarily, global variables can become a sinkhole for things that introduce tight coupling across your application.

The first thing to consider is that maybe you don't need a global variable or singleton. What would be the true harm if you created multiple instances of the potential singleton? What if you instead redesigned it to allow multiple instances? Sure, semantically there's only one master catalog in real life. But, you can design the Catalog class to encapsulate the books hash map as a static field. Your clients can create all the Catalog instances they want, but all of them point to the same static field. (This is a pattern known as monostate, by the way.)

For all the negativity about global variables and singletons, it's still a potentially legitimate construct. So, how do you know if your singleton is ill-conceived? The answer as always is testing!

You won't know whether your singleton is a problem until you try to test one of its clients. Suppose you are building a library API that interacts with the catalog, among other domain objects. Right now, such interactions probably aren't a problem, because the Catalog class is a simple hash map. But, what if the Catalog class interacted with a real database (see Listing 5)?

Listing 5: A dependency challenge.

import java.sql.*;
import java.util.*;

public class Catalog {
private static final Catalog soleInstance = new Catalog();

public static Catalog soleInstance() {
return soleInstance;
}

private Catalog() {
// enforce singularity
}

public void add(Book book) {
try {
Connection connection = ConnectionPool.get();
Statement statement = connection.createStatement();
statement.execute("insert into book // ...
// ...
}
}
//
}

Databases are always big headaches for testing. Of course, you want to verify that your Catalog class correctly interacts with Oracle and that all of your DDL is aligned with the Java code. But, you also want to verify the rest of the business logic in your application. You can do that using live interactions with the database, but such tests are very slow. They're also very painful to construct and maintain, for several reasons.

To test the rest of the classes in the system—the clients of Catalog—what you'd really like to do is to emulate the behavior of the Catalog class in what's known as a test double. At an abstract level, Catalog supports the ability to store and retrieve books associated with a specific classification. You want to mock that persistence behavior. Maybe all you really need is the map implementation above.

Unfortunately, the singleton construct says that you're stuck with the version of Catalog that mucks with JDBC. This is the chief problem with singleton: It prohibits you from creating a test double.

If you can't or won't redesign your singleton so that creating multiple instances is not harmful, there's another, better solution: Don't enforce the singleton. Right in the class file, add a comment (yes, a wonderful use of comments) to the constructor of Catalog:

public Catalog() {    // create only one, except for testing!
}

The reactions to this solution are sometimes very poor. "What if someone does create an instance?" Well, then, you have a bigger problems than the singleton. If developers won't read how to properly use a class, and heed the advice, why produce all that Javadoc that your shop no doubt requires? What's the point of code reviews, if they can't catch problems like this?

In fact, all sorts of conventions can easily be circumvented by a nefarious developer. Java even allows external access to private fields through reflection tricks! Sometimes, the best solution is to simply say, "don't do that."

You still can model the Catalog class similar to a singleton but provide hooks to allow it to cough up test doubles. You remove the private constructor from the Catalog class (see Listing 6). You provide a setInstance method to allow replacing the true, production singleton with a Catalog test double. Note the comment! You also can provide a reset method that tells the Catalog to begin using the production instance again. The reset ensures that your test doesn't gum up any other tests that expect to use the production Catalog instance.

import java.util.*;

public class Catalog {
private static final Catalog DEFAULT = new Catalog();
private static Catalog soleInstance = DEFAULT;

private Map books = new HashMap();

public static Catalog soleInstance() {
return soleInstance;
}

// use for testing
public static void setInstance(Catalog catalog) {
soleInstance = catalog;
}

public static void reset() {
soleInstance = DEFAULT;
}
// ...
}

In summary, don't throw away your singletons! Just make sure that they don't cause any unit testing challenges.

About the Author

Jeff Langr is a veteran software developer with over a quarter century of professional software development experience. He's written two books, including Agile Java: Crafting Code With Test-Driven Development (Prentice Hall) in 2005. Jeff is contributing a chapter to Uncle Bob Martin's upcoming book, Clean Code. Jeff has written over 75 articles on software development, with over thirty appearing at Developer.com. You can find out more about Jeff at his site, http://langrsoft.com, or you can contact him via email at jeff at langrsoft dot com.

0 comments: