I've used this 'pattern' a number of times, and thought I'd throw it out for anyone's benefit who might be interested.
In terms of Design Patterns, the singleton is cool - and that's not just because it's one of the few patterns that I get (jk).
I find, however, that in building HTTPModules I frequently find myself needing something with the following qualities:
- Accessible on a near global scale, almost like a Poor Man's 'Intrinsic'
- Contains fairly static data - that might shift or change, but the change is only brought about by persisting a change to a custom config file, or something else that's 'expensive'
- Speaking of expensive, I want to avoid having every request that comes through spin up one of these objects if it does something EXPENSIVE (like read from the web.config - or from a custom.config).
To satisfy all of these needs I've settled on the Cached Singleton - and I love it. Here's how it works.
1) Build your normal 'ConfigClass' or whatever it is like normal. Here's a pseudo-example of a FilterConfig (similar to the object I use in ReverseDOS):
public class FilterConfig
{
#region declarations // declare local variables etc. #endregion #region properties public bool IsAProperty
{
get { return true; }
}
public CustomType SomeProperty
{
get { return this._someProp; }
}
#endregion public FilterConfig()
{
// load in expensive stuff from the web.config // or from a database/etc. (deserialize a // custom class, or whatever) } public string[] GetStrings(string filter)
{
// custom logic/whatever } private void DoSomeHelperStuff() {}
2) Once that's complete, it's timed to add some spiffy singleton Logic. Just add a new static, read-only, property, of the type of the class itself, named Current
public static FilterConfig Current
{
get
{
FilterConfig config =
HttpContext.Current.Cache["keyName"] as FilterConfig;
if(config == null)
{
config = new FilterConfig();
config.LoadFromCustomConfigFile(FilterConfig.FilePath);
HttpContext.Current.Cache.Insert("keyName",
config,
new CacheDependency(FilterConfig.FilePath));
}
return config;
}
}
As you can see, a simple bit of custom logic attempts to grab the object out of the cache - and if it's not present a new object is instantiated, and directed to load info from a custom .config file (or other location) to derive state as needed. If the file indicated changes, then the object is immediately removed from the cache, and a subsequent hit will pop the updated version back into the cache.
Then to use it in your code - anywhere in your code - you simply grab a reference to it using the object's static Current Property:
FilterConfig current = FilterConfig.Current;
if(current.CheckForSomething(someVariable))
DoSomething();
Benefits:
- All the perf benefits of lazy-loading.
- Instantiation overhead is minimized (this can be a big win if you're deserializing XML, etc.)
- Ubiquity - because it's accessed statically, it's like having a global instance without all the worry/bother of binding a class into your HTTPApplication or global.asax.
Drawbacks:
- It's not a true Singleton - because it can be subverted (as documented here) in the sense that a developer could just instantiate their own instance of the object repeatedly within the same page if they were so inclined. Of course, a bit of modification would allow the object to behave like a true singleton (with a protected constructor, etc.) and you could therefore overcome this issue.
- ??? (let me know)
I totally agree that as far as patterns go, the singleton is awesome for web apps (I used this twice in my state module recently).
Any particular reason you left the constructor public instead of private or protected? Just wondering (obviously intentional as you pointed this out in your drawback).
Posted by: Brian | November 05, 2005 at 12:32 AM
"Any particular reason you left the constructor public instead of private or protected?"
Uhhhh. Cuz my brain was obviously unplugged. I was sorta clued in to that down below... but... well. yeah.
Posted by: Michael K. Campbell | November 05, 2005 at 08:11 AM
You really should update this example with concurrency issues addressed.
If your site gets hit with 50 requests at roughly the same time and all spend a millisecond or two in IO operations reading your config file, you'll have your "singleton" get created up to 50 times.
Posted by: Adam | November 07, 2005 at 07:46 AM
Good call Adam. Thanks for the feedback.
Posted by: Michael K. Campbell | November 07, 2005 at 09:02 AM