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)