воскресенье, 17 апреля 2016 г.

Singleton в C#

В этом посте мы рассмотрим следующие вещи :

  • Что такое паттерн проектирования Singleton (одиночка) ?
  • Как запрограммировать "одиночку" в C# ?
  • Эффекты многопоточности на паттерне Singleton
  • Реализация двойной блокировки при программировании "одиночки"
  • Раннее создание экземпляра "одиночки"
  • Полноценное "ленивое" создание "одиночки"
  • Программирование "одиночки" с применением обобщений С#
  • Создание одиночки через Lazy<T>
  • Пути взлома "одиночки"
  • Использование "одиночки"
  • Singleton как антипаттерн
  • Различия между статическим классом и паттерном Singleton
  • Заключение

Паттерн проектирования Singleton:

Шаблон (паттерн) проектирования Singleton гарантирует единственность экземпляра класса и предоставляет глобальный доступ к его свойставм и методам.

Стандартная реализация шаблона Singleton

Чтобы создать ровно один экземпляр "одиночки", мы делаем конструктор приватным. Теперь мы можем инстанцировать класс только внутри самого класса.
Мы создаем статическое поле, которое будет содержать созданный экземпляр класса.
Затем мы создаем статический метод, который представляет экземпляр класса для внешнего доступа. В методе проверяется существует ли экземпляр класса, и если нет, то создает и возвращает его, а в противном случае просто возвращает созданный ранее объект.

 private static Singleton _instance;
 
 private Singleton() {}
 
 public static Singleton GetInstance()
 {

  if(_instance == null)
                {
   _instance = new Singleton();
  }
  return _instance;

 }

Проблемы со стандартной реализацией

Стандартная реализация прекрасно работает в однопотоковой среде. В многопотоковой среде метод GetInstance() даст сбой :

public static Singleton GetInstance()
{

   if(_instance == null)
   {
      _instance = new Singleton();

   }
  return _instance;
}
Если два потока вызовут условие в операторе "if" в один и тот же момент времени, то будет создано два экземпляра класса Singleton.

Решение проблемы многопоточности в стандартной реализации одиночки

Мы можем синхронизировать код метода таким образом, что только одна нить будет владеть кодом в один момент времени. В коде ниже нить блокирует объект и проверяет, не был ли уже создан ранее экземпляр объекта.

  public sealed class Singleton
    {
        private static Singleton _instance = null;
        private static readonly object _lock = new object();
 
        private Singleton() {}
 
        public static Singleton GetInstance
        {
            get
            {
                lock (_lock)
                {
                    if (_instance == null)
                    {
                        _instance = new Singleton();
                    }
                    return _instance;
                }
            }
        }
}
Такой код решает проблему. Но это замедляет работу, потому что все прочие потоки ожидают завершения блокировки. Для этого мы используем следующие подходы :

1 : Двойная проверка при блокировке

Этот подход предусматривает изначальное существование экземпляра, и если экземпляр не существует, создает его под блокировкой.

 public sealed class Singleton
    {
        private static Singleton _instance = null;
        private static readonly object _lock = new object();
 
        private Singleton() {}
 
        public static Singleton GetInstance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_lock)
                    {
                        if (_instance == null)
                        {
                            _instance = new Singleton();
                        }
 
                    }
                }
                return _instance;
            }
        }
   }
Примечание. Подобный вопрос попался мне на экзамене по С# на сертификат Microsoft.

2 : Заранее созданный экземпляр

Здесь мы создаем экземпляр "одиночки" во время загрузки класса. Потокобезопасность осуществляется в этом случае без блокирования кода оператором lock.

public class Singleton 
{
 //создание экземпляра немедленно
 private static Singleton _instance = new Singleton();
 
 private Singleton() {}
 
 public static Singleton Default
 {
     get { return _instance; } //просто возвращаем экземпляр
 }

}

3 : Полноценное отложенное создание экземпляра

В этом примере инстанцирование происходит при первом обращении к статическому свойству Default класса Singleton.

 public sealed class Singleton
  {
    private Singleton()
    {
    }
 
    public static Singleton Default { get { return GetInstance._instance; } }
        
    private class GetInstance
    {
        // Явный статический конструктор сообщит компилятору
        // не инициализировать поле заранее
        // и поле будет инициализировано при первом к нему обращении 
        static GetInstance()
        {
        }
 
        internal static readonly Singleton _instance = new Singleton();
    }
 }
 

4 : Реализация паттерна Singleton с применением обобщений

Здесь будет создан единственный экземпляр и загрузка эффективно будет отложена, потому что конструктор не вызовется пока не будет вызван Build().

 public class Singleton<T> where T : new()
    {
        private static T _instance = new T();
 
        public T Default()
        {
            get { return _instance; }
        }
    }
 
...
    Singleton<SimpleType> instance = new Singleton<SimpleType>();
    SimpleType simple = instance.Default;

5 : Применение Lazy<T> type

Передавая в конструктор Lazy делегат (или, что проще, лямбда-выражение), в котором вызывается конструктор одиночки, мы получаем отложенное создание экземпляра.

public sealed class Singleton
{
    private static readonly Lazy<Singleton> _lazy =
        new Lazy<Singleton>(() => new Singleton());
    
    public static Singleton Default{ get { return _lazy.Value; } }
 
    private Singleton()
    {
    }
}

Обходные пути для взлома Singleton - вопросы, которые задают на собеседовании :

Даже если мы предотвратим создание нескольких экземпляров,используя двойную проверку при блокировке или мгновенное создание экземпляра, экземпляры все равно можно будет создать через :

– клонирование
– рефлексия
– использование класса singleton в качестве подкласса

Как избежать создания экземпляра Singleton через клонирование объекта ?

Через метод clone() можно создать копию объекта.

Чтобы предотвратить создание клонов экземпляра "одиночки", необходимо сделать следующее :

– Реализовать метод MethodwiseClone()
– Переопределить метод clone() и возбудить оттуда исключение CloneNotSupportedException.
 protected object MemberwiseClone()
    {
        throw new Exception("Cloning a singleton object is not allowed");
    }

 

Как предотвратить создание экземпляра Singleton через рефлексию ?

Чтобы предотвратить создание экземпляра через рефлексию, надо возбудить исключение из конструктора, если экземпляр уже имеется.

 private Singleton()
    {
        if (_instance != null)
        {
            throw new Exception("Cannot create singleton instance through reflection");
        }
    }

 

Как предотвратить создания экземпляра "одиночки" в качестве подкласса ?

Если основной класс имеет приватный конструктор, то подкласс не может его вызвать для создания экземпляра.

Для чего используется паттерн проектиорвания Singleton :

Логирование :

Шаблон Singleton хорошо подходит для реализации протоколирование, если мы планируем единственный файл лога для всех сообщений.

Кеширование:

Шаблон Singleton может быть использован в реализации механизма кеширования для предоставления глобального доступа к кешу.

Почему "одиночка" считается антипаттерном?

– "Одиночки" не так просто покрывать модульным тестированием, из-за невозможности контролировать инстанцирование и поддержку состояния между вызовами.
– Невозможно освободить память выделенную под экземпляр "одиночки".
– В многопотоковой среде доступ к "одиночке" может быть приостановлен из-за блокирования кода.
– "Одиночки" подразумевают тесную связь между классами, что опять-таки затрудняет тестирование

Отличия "одиночки" от статического класса :

– В C# в статическом классе нельзя реализовать интерфейс. В случае, если "одиночка" должен реализовать интерфейс, например для использования в IoC, вы можете не использовать статический класс
– Singleton можно клонировать, объект статического класса клонировать нельзя
– Объект Singleton хранится в куче, статический - в стеке
– Можно реализовать отложенную или асинхронную инициализацию "одиночки", а статический класс инициализируется при первой загрузке.

Заключение:

Итак, мы обусдили:
  • Различные способы программирования синглтона в C#
  • Использование паттерна синглтона
  • Почему синглтон является антипаттерном?
  • Различия между статическим классовм и синглтоном

воскресенье, 3 апреля 2016 г.

Распределение ролей в команде разработчиков SharePoint

При работе над проектами SharePoint, в команде разработчиков целесообразно распределить следующие роли:
  1. Руководитель проекта: Ведет проект до успешного внедрения посредством зарекомендовавших себя методологий.
  2. Архитектор: Технический специалист, который отвечает за концептуальную разработку решения в целом, взаимодействуя с остальными командными игроками.
  3. Инфраструктурщик и/или
  4. Разработчик: Расширяет возможности SharePoint посредством кодирования или встраиванием скриптов / кода наподобие кастомизации (JavaScript, XML, и т.п)
  5. Конфигураст: Его главной задачей является конфигурирование SharePoint и выкладывание страниц без написания кода. Разумеется, вы как разработчик или инфраструктурщик можете все это сделать и сами, но стоит ли это средств, потраченных на оплату вашего труда?
  6. Дизайнер/Информационный архитектор: На больших проектах могут быть заняты занимаются два разных специалиста. На средних проектах один человек может успешно справляться с двумя ролями, тем более после надлежащего обучения. Дизайнер UX представляет решение с точки зрения пользователя, разбираясь в том, как пользователи будут работать с сайтом, а затем проектируя навигацию, переходы между страницами, разметку страниц. Информационный архитектор анализирует суть информации, которую вы собираетесь хранить на данный момент или в будущем, а также предлагает к рассмотрению информацию, которую вы не планировали пока хранить, а затем разрабатывает такие вещи, как типы контента, метаданные, поля, значения, контролы и т.п. для структурирования и управления такой информацией.
  7. Производитель контента: Известные последние слова множества неудачных проектов на SharePoint звучат примерно так: “Не беспокойтесь, контент находится под контролем.” Хороший проект на SharePoint требует, чтобы кто-то отвечал за то, что полезный контент был своевременно создан и добавлен. Заметьте: лучшие решения требуют исполнения этой роли от многих людей.

суббота, 2 апреля 2016 г.

Структура решения SharePoint в Visual Studio

Одним из способов успешного создания решения SharePoint является разбиение решения на отдельные зависимые проекты:

  • Branding
    В данном проекте находятся все файлы, относящиеся к UI - пользовательскому интерфейсу, в первую очередь изображения и стили css. С этим проектом в основном имеет дело разработчик UI/UX. Как правило в проекте не должно содержаться ни одной строчки кода .NET, что означает отсутствие сборок для развертывания.
  • Presentation
    В этом проекте должны находиться веб-части, мастер-страницы, страницы приложения (application pages), элементы управления, и т.п. Это в первую очередь компоненты, которые могут быть исплозованы в разных частях решения. Проект не должен явно зависеть от проекта Branding.
  • Definition
    Этот проект содержит типы, столбцы сайта, типы полей, определения списков (list definitions) и сайтов (site definitions), а также шаблоны web. Проект имеет зависимость от проектов Presentation и Branding
  • Core
    Этот проект содержит, как правило, единственную сборку, которая представляет главную функциональность, используемую другими проектами. Это можно воспринимать, как бибилиотеку утилит или класс бизнес-логики. В большинстве случаев, проект не должен содержать никаких артефактов SharePoint. Исключение могут составлять файлы JavaScript, которые выступают как утилиты.
  • Resources
    Здесь содержатся все файлы языковых ресурсов, используемых остальными проектами. Оформив их в виде отдельного проекта, вы упростите перевод и организацию ресурсов для всего решения.
  • Others
    Прочие проекты, в зависимости от типа решения. Предположим, проект для рабочих процессов (workflows).

Как вы возможно заметили, при разработке решения в виде подобной структуры, могут возникнуть проблемы среди разработчиков, работающих на одном проекте. В первую очередь это касается добавления и удаления файлов, потому что при этом происходит обновления файла проекта csproj. Здесь на помощь придет система контроля версий TFS и т.п., что поможет наладить необходимый мерджинг.

Преимущество структурирования всех проектов подобно вышеописанной схеме заключается в том, что каждый разработчик будет в курсе того где какой находится артефакт, вне зависимости от того, какое решение разрабатывается. Так что, если вам требуется разбить функциональность, модуль или что-либо подобное в отдельное решение, снова используйте данную структуру.

Создание нескольких WSP для одного решения также потребует некоторой стандартизации развертывания, позволяющей проконтролировать пререквизиты при установке решения. Имеется множество скриптов для развертывания, доступных на CodePlex. Если вам интересно, гляньте проект "SharePoint Solution Deployer" по ссылке http://spsd.codeplex.com