Видео курс Шаблоны проектирования

Основные темы, рассматриваемые на уроке:
00:12 1 Общие сведения о паттерне (вступление)
03:35 2 Метафора
08:24 3 Структура паттерна на языке UML
09:07 4 Структура паттерна на языке C#
11:47 5 Участники и мотивация
12:18 6 Назначение паттерна
18:14 7 Использование паттерна

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

Для просмотра полной версии видеокурса и получения доступа к дополнительным учебным материалам Вам необходимо оформить подписку
Оформить подписку

Паттерн Singleton

Название

Одиночка

Также известен как

Solitaire (Холостяк)

Классификация

По цели: порождающий

По применимости: к объектам

Частота использования

Выше средней    -   1 2 3 4 5

Назначение

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

Введение

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

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

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

Структура паттерна на языке UML

См. пример к главе: \005_Singleton\001_Singleton

Структура паттерна на языке C#

См. пример к главе: \005_Singleton\001_Singleton

Участники

  • Singleton  - Одиночка:

Предоставляет интерфейс (метод Instance) для получения доступа к единственному экземпляру.

Отношения между участниками

Отношения между классами

  • Класс Singleton не имеет обязательных связей отношений.

Отношения между объектами

  • Клиент получает доступ к экземпляру через вызов метода Instance на классе-объекте Singleton.  

Мотивация

Для некоторых классов требуется, чтобы у них мог существовать только один экземпляр их класса. Например, класс Console, который представляет собой объектно-ориентированное представление сущности – консоль, из объективной реальности. В объективной реальности консоль, это собирательное понятие, объединяющее два устройства – клавиатуру и экран монитора (устройства ввода/вывода информации). 

Класс Console, является статическим классом, а это значит, что невозможно создать переменное количество его экземпляров. Поэтому можно сказать, что статические классы в C# являются одним из возможных выражений паттерна Singleton.

Применимость паттерна

Паттерн Singleton рекомендуется использовать, когда:

  • В системе должен быть только один экземпляр некоторого класса, или в частном случае заранее определенное пользователем количество экземпляров (два, три и т.д.).
  • Требуется организовать расширение класса единственного экземпляра через использование механизма наследования.

Результаты

Особенности применения паттерна Singleton:

  • Контроль доступа к единственному экземпляру.

Процесс создания единственного экземпляра скрыт в классе Singleton, поэтому класс Singleton полностью контролирует доступ к экземпляру через использование метода Instance, который всегда возвращает ссылку на один и тот же экземпляр.

  • Возможность расширения через наследование.

Если класс Singleton не является статическим или герметизированным / запечатанным (sealed), то от него возможно наследование, что позволит расширить существующую функциональность.

  • Возможность наличия переменного числа экземпляров.

Паттерн Singleton позволяет создавать фиксированное число экземпляров класса Singleton, например, только два или три и т.д.

См. пример к главе: \005_Singleton\002_MultySingleton

  • Большая гибкость чем у статических классов.

Одним из вариантов реализации паттерна Singleton в C#, является использование статических классов. Но такой подход может в дальнейшем препятствовать изменению дизайна в том случае, если понадобится использование нескольких экземпляров класса Singleton. Кроме того, статические классы не сопрягаются с механизмами наследования и статические методы не могут быть виртуальными, что не допускает полиморфных отношений.

Реализация

Особенности, которые следует учесть при реализации паттерна Singleton:

  • Гарантия наличия единственного экземпляра.

Паттерн Singleton устроен так, что при его применении не удастся создать более одного экземпляра определенного класса. Чаще всего для получения экземпляра используют статический фабричный метод Instance, который гарантирует создание не более одного экземпляра. Если метод Instance будет вызван впервые, то он создаст экземпляр при помощи защищенного конструктора и вернет ссылку на него, при этом сохранив эту ссылку в одном из своих полей. При всех последующих вызовах метода Instance, будет возвращаться ранее сохраненная ссылка на существующий экземпляр.

См. пример к главе: \005_Singleton\001_Singleton

  • Наследование от Singleton.

Процесс создания производного класса SingletonDerived довольно прост, для этого достаточно заместить в нем статический метод Instance, в котором унаследованной из базового класса переменной instance присвоить ссылку на экземпляр производного класса SingletonDerived.

public new static DerivedSingleton Instance()
        {
            if (uniqueInstance == null)
                uniqueInstance = new DerivedSingleton();

            return uniqueInstance as DerivedSingleton;
        }

См. пример к главе: \005_Singleton\003_SingletonInheritance

Если в программе имеется несколько производных классов-одиночек (DerivedSingleton1, DerivedSingleton2, …) и далее потребуется организовать выбор использования определенного производного класса-одиночки, то для выбора нужного экземпляра-одиночки удобно будет воспользоваться реестром одиночек, который представляет собой ассоциативный массив (ключ-значение). В качестве ключа может выступать имя одиночки, а в качестве значения – ссылка на экземпляр определенного одиночки.

См. пример к главе: \005_Singleton\004_RegistSingleton

  • Потокобезопасный Singleton

Может возникнуть ситуация, когда из разных потоков происходит обращение к свойству Singleton Instance, которое должно вернуть ссылку на экземпляр-одиночку и при этом экземпляр-одиночка еще не был создан и его поле instance равно null. Таким образом, существует риск, что в каждом отдельном потоке будет создан свой экземпляр-одиночка, а это противоречит идеологии паттерна Singleton. Избежать такого эффекта возможно через инстанцирование класса Singleton в теле критической секции (конструкции lock), или через использование «объектов уровня ядра операционной системы типа WaitHandle для синхронизации доступа к разделяемым ресурсам».

class Singleton
    {
        private static volatile Singleton instance = null;
        private static object syncRoot = new Object();

        private Singleton() { }

        public static Singleton Instance
        {
            get
            {
                Thread.Sleep(500);

                if (instance == null) 
                {
                    lock (syncRoot) // Закомментировать lock для проверки.
                    {
                        if (instance == null)
                            instance = new Singleton();
                    }
                }

                return instance;
            }
        }
    }

См. пример к главе: \005_Singleton\005_MultithreadedSingleton

  • Сериализация Singleton.

В редких случаях может потребоваться сериализовать и десериализовать экземпляр класса Singleton. Например, для сериализации и десериализации можно воспользоваться экземпляром класса BinaryFormatter. Сам процесс сериализации и десериализации не вызывает технических трудностей. Единственный момент, на который следует обратить внимание — это возможность множественной десериализации, когда сериализованный ранее экземпляр-одиночка десериализуется несколько раз в одной программе. В таком случае, может получиться несколько различных экземпляров одиночек, что противоречит идеологии паттерна Singleton. Чтобы предотвратить возможность дублирования экземпляра-одиночки при каждой десериализации, рекомендуется использовать подход, при котором сериализуется и десериализуется не сам экземпляр-одиночка, а его суррогат (объект-заместитель). 

BinaryFormatter formatter = new BinaryFormatter();
            formatter.SurrogateSelector = Singleton.SurrogateSelector;

Использование объекта-суррогата дает возможность организовать тонкий контроль над десериализацией экземпляра-одиночки.

// Класс Singleton не должен сериализовываться напрямую 
    // и не должен иметь атрибута [Serializable]! 
    public sealed class Singleton
    {
        private static Singleton instance = null;
        public String field = "Some value";

        public static Singleton Instance
        {
            get { return (instance == null) ? 
                          instance = new Singleton() : instance; }
        }

        public static SurrogateSelector SurrogateSelector
        {
            get
            {
                var selector = new SurrogateSelector();
                var singleton = typeof(Singleton);
                var context = new StreamingContext(StreamingContextStates.All);
                var surrogate = new SerializationSurrogate();

                selector.AddSurrogate(singleton, context, surrogate);
                return selector;
            }
        }

        // Nested class
        private sealed class SerializationSurrogate : ISerializationSurrogate
        {
            // Метод вызывается для сериализации объекта типа Singleton
            void ISerializationSurrogate.GetObjectData(Object obj, 
                               SerializationInfo info, StreamingContext context)
            {
                Singleton singleton = Singleton.Instance;
                info.AddValue("field", singleton.field);
            }

            // Метод вызывается для десериализации объекта типа Singleton
            Object ISerializationSurrogate.SetObjectData(Object obj, 
                                                   SerializationInfo info, 
                                                   StreamingContext context, 
                                                   ISurrogateSelector selector)
            {
                Singleton singleton = Singleton.Instance;
                singleton.field = info.GetString("field");
                return singleton;
            }
        }
    }

См. пример к главе: \005_Singleton\006_SerializationSingleton

  • Singleton с отложенной инициализацией.

Чаще всего метод Instance использует отложенную (ленивую) инициализацию, т.е., экземпляр не создается и не хранится вплоть до первого вызова метода Instance. Для реализации техники отложенной инициализации в C# рекомендуется воспользоваться классом Lazy<T>, причем по умолчанию экземпляры класса Lazy<T> являются потокобезопасными.

class Singleton
    {
        static Lazy<Singleton> instance = new Lazy<Singleton>();

        public static Singleton Instance
        {
            get
            {
                return instance.Value;
            }
        }
    }

См. пример к главе: \005_Singleton\007_LazySingleton

Пример кода игры «Лабиринт»

В игре Лабиринт классом с единственным экземпляром может быть класс MazeFactory, который строит лабиринт. Легко понять, что для расширения лабиринта путем строительства большего числа комнат со стенами и дверьми не нужно каждый раз строить новую фабрику, всегда можно использовать одну и ту же уже имеющуюся фабрику. Таким образом, сам класс MazeFactory будет контролировать наличие одного единственного своего экземпляра mazeFactory, и будет предоставлять доступ к этому экземпляру путем использования метода Instance.

public static MazeFactory Instance()
        {
            if (instance == null)
            {
                // Берем значение свойства MAZESTYLE из файла окружения
                string mazeStyle = GetEnv("MAZESTYLE");

                // 0 - совпадают, 1 - не совпадают
                if (string.Compare(mazeStyle, "bombed") == 0) 
                {
                    Console.WriteLine("Фабрика для лабиринта с бомбами");
                    instance = new BombedMazeFactory();
                }
                else if (string.Compare(mazeStyle, "enchanted") == 0)
                {
                    Console.WriteLine("Фабрика для лабиринта с заклинаниями");
                    instance = new EnchantedMazeFactory();
                }
                else // По умолчанию.
                {
                    Console.WriteLine("Фабрика для обычного лабиринта");
                    instance = new MazeFactory();
                }
            }
            return instance;
        }

Следует заметить, что в данной реализации, метод Instance нужно модифицировать при определении каждого нового подкласса MazeFactory. Такой подход является неприемлемым, поскольку не обеспечивает необходимую гибкость разработки. Вариантами решения данной проблемы являются использование принципов «реестра одиночек» и отложенной (ленивой) инициализации, тогда приложению не нужно будет загружать все неиспользуемые подклассы.

Известные применения паттерна в .Net

System.ServiceModel.ServiceHost

http://msdn.microsoft.com/ru-RU/library/system.servicemodel.servicehost.singletoninstance(v=vs.100).aspx

System.Data.DataRowComparer

http://msdn.microsoft.com/ru-ru/library/system.data.datarowcomparer.aspx

© 2017 ITVDN, все права защищены