Singleton Pattern
In the beginning, there was...
the singleton.
Everything's got to start somewhere. Just like there's only one object type to “rule them all”, the singleton represents cases where there's only one.
A pair of examples are the TextWriter instances that represents Standard Out and Standard Error. You only need one instance for each of those, for your entire program. It makes sense for all the code in your program, all the threads, all of ___(insert something here)_____, to share. Since there's only one for the entire process, then you might as well represent it that way in your library or codebase.
Like the proverbial skinning of the cat, there are a bunch of ways to do this. Here's a common one, say our type is Foo.
|
class Foo {
// change the default constructor to private
private Foo() { }
public static readonly Foo Only = new Foo();
public object DoFunctionality();
} |
The above method is short and simple. Here are the important things to take away:
(1) no public constructors: The point is to share instances, not to let people create their own. The lack of public constructors indicates to developers that this is not your “run of the mill” type.
(2) immutable, immutable, immutable! nine times out of ten you don't want anything you do to this object to affect other code, threads, etc., in some inadvertent manner. Another word for this is to make your type a functional type (no new ideas to functional programmers, who are used to dealing with singletons all the time).
A more common thing that people will find in the .NET libraries is this implementation:
|
class Foo {
// change the default constructor to private
private Foo() { }
private static readonly Foo sharedOnly = new Foo();
public static Foo Only {
get { return sharedOnly; }
}
public object DoFunctionality();
} |
It hides the shared instance of Foo behind a property. Library developers may choose this implementation since allows them to decide later exactly how to return their singleton instance. In fact, in the gang of four design patterns book, the template they outlined is more similar to this, since their examples are in C++.
Another point to take in mind in regards to hiding your instance behind a property (or a method call) is that the compiler can no longer prove that two different accesses will always return the same instance of the object. It's then up to users of this type to trust the implementation, but then again, it's another possible “bug” if your design explicitly wants only ONE instance.
Yet another common thing to do, if you've decided to abstract out the singleton behind a property is to delay its creation until needed, like so:
|
class Foo {
// change the default constructor to private
private Foo() { }
private static Foo sharedOnly = null;
public static Foo Only {
get {
return (sharedOnly != null) ? sharedOnly :
(sharedOnly = new Foo());
}
}
public object DoFunctionality();
} |
Some people may not like the ternary (? :) expression, or the assignment used as an expression, but I think it's perfectly legit in something this simple. What you'll notice changed is the removal of the readonly attribute, since you're instantiating on demand. Although you're arguably gaining since you don't instantiate objects that you don't need for sure, you're also still opening yourself up for bugs by removing the readonly attribute (such as accidentally setting that field).
If you don't care about getting back the exact same instance, you may wish to statically refer to something similar to the lazy loader posted to JayBaz's blog, written by Cyrus, before he got his own blog. But stay tuned to why I think IOptional<T> should be implemented differently. It's been a little over the 48 hours Cyrus promised me to write this all up, but I've since then gotten an extension until morning :-P.
Others may point out that using either of the examples that employ the use of properties may slow down the program since you're actually executing code whenever you reference a property -- but this is usually trivial overhead, and one can probably assume that the JIT will optimize out the function call.
I bet you're probably thinking that I'm beating a dead horse, but I wanted to make sure to cover a lot of possible design choices and the pros and cons of each. Next concept in the slate: unions.