Тема реализации шаблона Singleton так или иначе затрагивает жизнь любого разработчика – толи на собеседовании спросят (или самом спросить придется), толи в реальном проекте найдется применение (не приведи Господи). О том, как этого зверя вырастить, написано немало – и в MSDN, и в прочих интернетах (последняя статься считается канонической и ранее располагалась по другому адресу). В какой-то момент казалось (мне по крайней мере), что придумать что-нибудь новое в этой области невозможно. Ошибся.
Недавно на одном из проектов заказчик прислал кусок [псевдо]кода, в котором кроме всего прочего содержалась крайне любопытная реализация привычного шаблона (тот еще затейник – архитектура решения тоже заставляем “много думать”). В чистом виде (убирая конкретику проекта и применяя привычные для .NET схемы именования и форматирования) это выглядит так:
public interface ISingleton
{
}
class Singleton : ISingleton
{
public delegate ISingleton InstanceGetterDelegate();
private static Singleton _singleton;
private static InstanceGetterDelegate _instanceGetter;
static Singleton()
{
var type = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType;
// assign the closure that executes on the first call of GetInstance() method
_instanceGetter = () =>
{
Singleton instance = null;
lock (type)
{
var rslt = Interlocked.CompareExchange<Singleton>(ref _singleton, null, null);
if (rslt == null) // indicates is first call
{
instance = new Singleton(/* init arguments go here*/);
Interlocked.CompareExchange<Singleton>(ref _singleton, null, instance);
// now replace with a closure that executes on all subsequent calls of GetInstance() method
InstanceGetterDelegate fn = () =>
{
return Interlocked.CompareExchange<Singleton>(ref _singleton, null, null);
};
Interlocked.Exchange<InstanceGetterDelegate>(ref _instanceGetter, fn);
}
else
{
instance = rslt; // was already set with singleton instance
}
}
return instance;
};
}
private Singleton() { } // disable default instance constructor
public static ISingleton GetInstance() { return _instanceGetter(); }
}
И вот тут, что называется, “накрывает”. Я бы назвал этот способ “JavaScript-style Singleton” (конечно же за использования замыканий). В целом (за исключение блокировки по Type (о чем я уже писал) и использования рефлексии в статическом конструкторе) реализация выглядит любопытной и уж во всяком случае расширяет горизонт.
HTH, AlexS
Вы правы, действительно "накрывает" :)
ОтветитьУдалитьИдея подмены делегата действительно свежая (по крайней мере для меня). Все остальное на мой взгляд полный булщит :)
Мои комментарии:
1) Все вызовы Interlocked.Exchange абсолютно бесполезны т.к. уже обернуты Lock'ом.
2) InstanceGetterDelegate() и GetInstance() должны возвращать Singleton, а не ISingleton иначе прийдется кастовать...
3) ISingleton в большинстве случаев вообще не нужен
Итого мы имеем:
class Singleton : ISingleton
{
public delegate Singleton InstanceGetterDelegate();
private static Singleton _singleton;
private static InstanceGetterDelegate _instanceGetter;
private static readonly Object _syncObj = new Object();
static Singleton()
{
// assign the closure that executes on the first call of GetInstance() method
_instanceGetter = () =>
{
lock (_syncObj)
{
if (_singleton == null) // indicates is first call
{
_singleton = new Singleton(/* init arguments go here*/);
// now replace with a closure that executes on all subsequent calls of GetInstance() method
_instanceGetter = () =>
{
return _singleton;
};
}
}
return _singleton;
};
}
private Singleton() { } // disable default instance constructor
public static Singleton GetInstance() { return _instanceGetter(); }
}
p.s. С lock(type) не все однозначно.
Вот тут (http://msdn.microsoft.com/en-us/library/c5kehkcz(VS.71).aspx) например сказано следующее:
"Typically, expression will either be this, if you want to protect an instance variable, or typeof(class), if you want to protect a static variable (or if the critical section occurs in a static method in the given class)."
а тут (http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx) вот что:
"In general, avoid locking on a public type, or instances beyond your code's control. The common constructs lock (this), lock (typeof (MyType)), and lock ("myLock") violate this guideline:
lock (this) is a problem if the instance can be accessed publicly.
lock (typeof (MyType)) is a problem if MyType is publicly accessible.
lock("myLock") is a problem because any other code in the process using the same string, will share the same lock."
Сначала о главном: приведенный мною код - это очищенная от конкретики иллюстрация идеи, подсмотренной в одном из проектов. При этом автором кода я не являюсь. Отсюда - и Interlocked (в "прототипе" так и было), и блокировка, и интерфейс (в оригинале он был наполнен смыслом).
ОтветитьУдалитьВозвращаясь к вопросам:
1) Да, Interlocked тут не особо помогает, но то сколько из-за lock, сколько потому, что присваивания и так атомарны (спасибо одному коллеге, что напомнил ;-) )
2) Не понял мысли на счет преобразования типов. Не вижу проблемы в том, что делегаты возвращают ISingleton - наш класс Singleton реализует этот интерфейс, поэтому все будет в порядке.
3) Интерфейс был в "оригинале", поэтому остался и тут (он добавляет веселья, но на сам пример большого влияние не оказывает).
Саша, ты меня неправильно понял :)
ОтветитьУдалитьЯ критиковал не тебя, а код и прекрасно понимаю, что ты его выложио "как есть".
Возвращаясь к вопросам:
1) Даже если присвоение были бы не атомарны Interlocked все равно не нужен т.к. в Lock мы все равно не пойдем больше одного раза.
2) Тут все просто. Это нужно что бы не писать лишний каст: Singleton singleton = (Singleton)Singleton.GetInstance();