среда, 1 сентября 2010 г.

Singleton in .NET 4 - вопрос снят?

Так как у нас на проекте есть открытые вакансии, то приходится активно участвовать в собеседованиях джаверов. В связи с этим хочется поделиться мыслями по одному из часто задаваемых вопросов - многопоточная реализация Singleton. Не смотря на то, что уже много копий сломано и много написано, редко когда удается услышать четкий и правильный ответ.

С моей точки зрения этот вопрос не так уж много показывает (скажем так, он кажется обманчиво интересным), но ответы на сопутствующие вопросы (более интересные) сами его провоцируют. Например, на вопрос "Какие вы знаете шаблоны проектирования", ответ обычно начинается с "Singleton, Factory..."

К сожалению, несмотря на то, что GoF имеет успех уже на протяжении более 15 лет, достаточно часто ответ на этом заканчивается. Кстати, на второй часто задаваемый вопрос "Чем отличается Factory от Abstract Factory" ответ дается еще реже.

Но давайте разберемся, стоит ли начинать ответ с Singleton? Наверное, не стоит. Вообще, многие считают, что Singleton - это anti-pattern. И в любом случае, Singleton - это далеко не самый интересный шаблон проектирования.

Что также хочется отметить, обычно просят реализовать просто Singleton, не упоминая ленивую инициализацию, однако почему-то на доске очень часто появляется double checked locking. Это плохой стереотип - все так делают, значит, так нужно. В реальной жизни ленивая инициализация чаще всего не требуется, а еще чаще стоимость многопоточной реализации Singleton (потому что у дополнительной синхронизации есть стоимость) превышает экономию на отложенной инициализации.

Так вот, к чему это я все? :) Проблема настолько избита, что в .NET 4.0 ввели специальный класс, который наконец-то дает нам out-of-the-box реализацию, - Lazy, и реализация значительно упрощается:

   1: public class LazySingleton
   2: {
   3:     // static holder for instance, need to use lambda to construct since constructor private
   4:     private static readonly Lazy<LazySingleton> _instance
   5:         = new Lazy<LazySingleton>(() => new LazySingleton());
   6:  
   7:     // private to prevent direct instantiation.
   8:     private LazySingleton()
   9:     {
  10:     }
  11:  
  12:     // accessor for instance
  13:     public static LazySingleton Instance
  14:     {
  15:         get
  16:         {
  17:             return _instance.Value;
  18:         }
  19:     }
  20: }

Всё, готово. Код честно взят отсюда. Кстати, заметьте, что код на самом-то деле глупость - у нас пустой конструктор, откладывать его исполнение за счет дополнительных синхронизаций бессмысленно.

Чуть побольше про ленивую инициализацию и Lazy можно почитать на MSDN здесь. Есть и на русском, но поверьте, оно вам на таком русском не нужно.

И вот казалось бы, одним вопросом для дотнетчиков меньше. Но что-то у меня есть подозрения, что вопрос еще какое-то время побудет актуальным :) Как считаете?