Other pattern focus posts:
Singelton is a well-used and commonly known pattern in the software development world. It's been around for a very long time and we are still using the same basic three or four rows of code to implement it over, over and over again. With some inspiration from one of my students on my LINQ course today I finally sat down and did something about it. Enter Singelton v2.0:
MyFactory factory1 = new StaticSingelton("initialization data");
MyFactory factory2 = new StaticSingelton("initialization data");
The two, factory1 and factory2, now shares the same instance of MyFactory. How did I do this? Read on to find out.
Inspired by Nullable I took on the task of creating a generic singelton class that wraps and manages the singelton instances for you. My first naive attempt look something like this:
public class NaivSingelton
where T: class, new()
{
private static T _instance;
public T Instance {
get {
if (_instance == null)
_instance = new T();
return _instance;
}
}
public static implicit operator T(NaivSingelton value) {
return value.Instance;
}
}
Which gave the simple and naive usage of:
MyFactory factory = new NaivSingelton();
However, that didn't satisfy me. I had a couple of more requirements. I wanted to change what the scope/context of the singelton should be. I also wanted be able to initialize the object with parameters and I wanted to be able to separate singelton instances based on what those parameters was. So utilizing some more generic code, the Activator class and a version of "Template Method" a simple base class took form:
public abstract class Singelton
{
protected class KeyHolder {
private object[] parameters;
internal KeyHolder(object[] parameters)
{
this.parameters = parameters;
}
public override bool Equals(object obj)
{
if (obj == null && parameters == null)
return true;
else if (obj == null)
return false;
if (!(obj is KeyHolder))
return false;
object[] paramsToCompareWith = ((KeyHolder)obj).parameters;
if (paramsToCompareWith.Length != parameters.Length)
return false;
for (int index = 0; index < parameters.Length; index++)
{
if (parameters[index] != paramsToCompareWith[index])
return false;
}
return true;
}
public override int GetHashCode()
{
int hashCode = 0;
foreach (var item in parameters)
{
hashCode = hashCode ^ item.GetHashCode();
}
return hashCode;
}
}
public Singelton() { }
public Singelton(T initialObject)
{
StoredInstances[key] = initialObject;
}
public Singelton(params object[] initializationParameters)
{
InitializationParameters = initializationParameters;
}
private object[] _initializationParameters;
private KeyHolder key = new KeyHolder(null);
protected object[] InitializationParameters
{
get
{
return _initializationParameters;
}
set
{
_initializationParameters = value;
key = new KeyHolder(value);
}
}
protected abstract Dictionary StoredInstances { get; }
public T Instance
{
get
{
if (!StoredInstances.ContainsKey(key))
StoredInstances[key] = CreateNewInstance();
return StoredInstances[key];
}
}
private T CreateNewInstance()
{
return (T)Activator.CreateInstance(typeof(T), InitializationParameters);
}
public static implicit operator T(Singelton value)
{
return value.Instance;
}
}
This now allowed me to create different types of singelton implementations that stored the singelton instances differently. Also using a dictionary and a separate Key class for that dictionary (thanks for the idea Roger) I was able to differentiate instances based on what initialization data that was passed to the constructor of the singelton class.
Now I could simply create new subclasses:
public class ThreadStaticSingelton : Singelton
where T : class
{
[ThreadStatic]
private static Dictionary _storedInstances = new Dictionary();
public ThreadStaticSingelton(T initialObject) : base(initialObject) { }
public ThreadStaticSingelton(params object[] initializationParameters) : base(initializationParameters) { }
protected override Dictionary StoredInstances
{
get
{
return _storedInstances;
}
}
}
And
public class StaticSingelton : Singelton
where T : class
{
private static Dictionary _storedInstance = new Dictionary();
public StaticSingelton(T initialObject) : base(initialObject) { }
public StaticSingelton(params object[] initializationParameters) : base(initializationParameters) { }
protected override Dictionary.KeyHolder, T> StoredInstances
{
get { return _storedInstance; }
}
}
Which gives me the possibility to initialize singeltons like this now:
MessageClass m1 = new StaticSingelton("Hello {0}");
m1.Say();
MessageClass m2 = new StaticSingelton("Hello {0}");
m2.Say();
MessageClass m3 = new StaticSingelton("Hello 2 {0}");
m3.Say();
MessageClass m4 = new StaticSingelton("Hello 2 {0}");
m4.Say();
M1 and M2 shares the same instance, while M3 and M4 shares another.
One could easily think about other possible singelton implementations that can easily extend the base singelton class and take advantage of the generic way in which it get's initialized.
You can download my example project over here: https://lowendahl.net/shoutCode.aspx
|