2009-05-27

Patterns and Practices - Singleton

One of my biggest shortcomings as a developer is not being able to identify the different Design Patterns out there. Sure, I've been writing code for years that have applied these same types of behaviours and methodologies, but I've never been able to say "See this code: Singleton... that one: Factory".

Well, I've got news for you. Patterns are not only a way to write code, but more importantly, they are a way to communicate it. Hire a new developer? Need to get him trained? He's working with your CRM's Business Layer? Tell him that it's instantiated by the Factory Pattern and he'll have an idea of what's up. Sure, you're implementation maybe slightly different--but he'll know. Patterns are, very simply, a way for developers to communicate what they're doing.

The first Pattern I'm going to talk about is the Singleton. Wikipedia has a pretty good definition... The Singleton is a design pattern to limit instantiation of a class to one object. Let's define a couple of OOP terms...

  • Class: in computer programming, a representation of a defined structure for a type of data. May contain Properties, Fields, and Behaviours/Methods. An abstract representation of a data type.
  • Object: in computer programming, an instance of a Class--most likely having Properties and Fields populated, and possibly having Behaviours/Methods. A concrete, tangible Instantiation of a Class.

So, the goal of the Singleton pattern is to limit the number of (object) instances of an Class to one and only one. During run time, there will ever be only one concurrent instance. (Note, it maybe possible to have your Singleton cleaned up during runtime--I won't be talking about that here).

The following is an implementation of the Singleton in C#.

public sealed class Banana
{
static Banana instance = null;

// Constructor
Banana() { }

// Call this property to consume
public static Banana Instance
{
get
{
if (instance == null)
{
instance = new Banana();
}
return Instance;
}
}
}

There is a problem with the Singleton Banana, in the above example. While it will technically work, it's not thread-safe. It's theoretically possible for this call to be made concurrently and two instantiations of the local variable instance to occur. If you know your application is never going to exploit this, you'll be fine. But if not, you may very well run into a temporal paradox that will ultimately lead to the destruction of the universe as we know it. You may wish to use something like this instead.

public sealed class Banana
{
static Banana instance = null;
static readonly object lockObject = new object();

// Constructor
Banana() { }

// Call this property to consume
public static Banana Instance
{
get
{
if (instance == null)
{

// use your lock object to isolate the object / thread.
lock (lockObject)
{

// check to make sure it's still null. It's possible that two
// threads got into the same lock code simultaneously
if (instance == null)
{
instance = new Banana();
}
}
}
return Instance;
}
}
}

Practical reasons to use it...

  • You have a piece of data that is pretty stagnant that needs to be read frequently (but seldom written to). Placing that data in memory would have certain advantages (in-memory index scans, for example) and/or a data-store lookup would be costly for this type of data.

Places where I've used it...

  • Querying Active Directory for a list of users in a group that's repeatedly shown to my users, but never written.
  • Keeping common information such as States or Departments in memory and available--thus reducing database lookups.
  • When working with data in a stateless environment (and I'm too lazy to implement a state management system).

What to be worried about...

  • Memory consumption. Since the Singleton exists throughout the scope of your application (after it's been instantiated, of course), it will use up X amount of space in memory indefinitely.
  • What if your data source changes? A Singleton may need to be refreshed. In that case, I've used a "DateQueried" property that you use to check against and, if expired, refresh your data.

More Patterns coming soon. Zoidberg away!

No comments: