Для чего нужен синглтон

Использование паттерна синглтон

Введение

Реализация

Первое, что хотелось бы отметить: синглтон — это реализация, а не интерфейс. Что это значит? Это значит, что класс по возможности должен использовать некий интерфейс, а то, будет там синглтон или нет, это он не знает и знать не должен, т.к. всякое явное использование синглтона и будет приводить к указанным проблемам. На словах выглядит хорошо, давайте посмотрим, как это должно выглядеть в жизни.

Для реализации данной идеи мы воспользуемся мощным подходом, который называется Dependency Injection. Суть его состоит в том, что мы неким образом заливаем реализацию в класс, при этом класс, использующий интерфейс, не заботится о том, кто и когда это будет делать. Его эти вопросы вообще не интересуют. Все, что он должен знать, это как правильно использовать предоставленный функционал. Интерфейс функционала при этом может быть как абстрактный интерфейс, так и конкретный класс. В нашем конкретном случае это неважно.

Идея есть, давайте реализуем на языке C++. Тут нам помогут шаблоны и возможность их специализации. Для начала определим класс, который будет содержать указатель на необходимый экземпляр:

Описанный класс решает несколько задач. Во-первых, он хранит указатель на необходимый экземпляр класса. Во-вторых, при отсутствии экземпляра вызывается функция anFill, которая заполняет нужным экземпляром в случае отсутствия такового (метод reinit). При обращении к классу происходит автоматическая инициализация экземпляром и его вызов. Посмотрим на реализацию функции anFill:

Таким образом по умолчанию данная функция кидает исключение с целью предотвращения использования незадекларированной функции.

Примеры использования

Теперь предположим, что у нас есть класс:

Мы хотим сделать его синглтоном для использования в различных контекстах. Для этого специализируем функцию anFill для нашего класса X:

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

Что выведет на экран:

При повторном вызове action мы увидим:

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

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

Что после предыдущих вызовов выведет на экран:

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

Т.е. мы заполняем класс Y нашим (известным) экземпляром и вызываем соответствующую функцию. На экране мы получим:

Разберем теперь случай с абстракными интерфейсами. Создадим абстрактный базовый класс:

Определим 2 различные реализации этого интерфейса:

По умолчанию будем заполнять, используя первую реализацию Impl1:

Таким образом, следующий код:

Создадим класс, использующий наш интерфейс:

Теперь мы хотим поменять реализацию. Тогда делаем следующее:

Что дает в результате:

Развитие идеи

В целом на этом можно было бы закончить. Однако стоит добавить немножко полезных макросов для облегчения жизни:

Многие могут сказать, что макросы — это зло. Ответственно заявляю, что с данным фактом я знаком. Тем не менее, это часть языка и ее можно использовать, к тому же я не подвержен догмам и предрассудкам.

Макрос DECLARE_IMPL декларирует заполнение, отличное от заполнения по умолчанию. Фактически эта строчка говорит о том, что для этого класса будет происходить автоматическое заполнение неким значением в случае отсутствия явной инициализации. Макрос BIND_TO_IMPL_SINGLE будет использоваться в CPP файле для реализации. Он использует функцию single, которая возвращает экземпляр синглтона:

Использование макроса BIND_TO_SELF_SINGLE говорит о том, что для класса будет использоваться экземпляр его самого. Очевидно, что в случае абстракного класса этот макрос неприменим и необходимо использовать BIND_TO_IMPL_SINGLE с заданием реализации класса. Данная реализация может быть скрыта и объявлена только в CPP файле.

Теперь рассмотрим использование уже на конкретном примере, например конфигурации:

Далее можно использовать в других классах:

Выводы

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

Источник

Подводные камни Singleton: почему самый известный шаблон проектирования нужно использовать с осторожностью

Для чего нужен синглтон. Смотреть фото Для чего нужен синглтон. Смотреть картинку Для чего нужен синглтон. Картинка про Для чего нужен синглтон. Фото Для чего нужен синглтон

Паттерн «Одиночка» — пожалуй, самый известный паттерн проектирования. Тем не менее, он не лишен недостатков, поэтому некоторые программисты (например, Егор Бугаенко) считают его антипаттерном. Разбираемся в том, какие же подводные камни таятся в Singleton’е.

Определение паттерна

Само описание паттерна достаточно простое — класс должен гарантированно иметь лишь один объект, и к этому объекту должен быть предоставлен глобальный доступ. Скорее всего, причина его популярности как раз и кроется в этой простоте — всего лишь один класс, ничего сложного. Это, наверное, самый простой для изучения и реализации паттерн. Если вы встретите человека, который только что узнал о существовании паттернов проектирования, можете быть уверены, что он уже знает про Singleton. Проблема заключается в том, что когда из инструментов у вас есть только молоток, всё вокруг выглядит как гвозди. Из-за этого «Одиночкой» часто злоупотребляют.

Простейшая реализация

Как уже говорилось выше, в этом нет ничего сложного:

Принцип единственной обязанности

В объектно-ориентированном программировании существует правило хорошего тона — «Принцип едиственной обязанности» (Single Responsibility Principle, первая буква в аббревиатуре SOLID). Согласно этому правилу, каждый класс должен отвечать лишь за один какой-то аспект. Совершенно очевидно, что любой Singleton-класс отвечает сразу за две вещи: за то, что класс имеет лишь один объект, и за реализацию того, для чего этот класс вообще был создан.

Принцип единственной обязанности был создан не просто так — если класс отвечает за несколько действий, то, внося изменения в один аспект поведения класса, можно затронуть и другой, что может сильно усложнить разработку. Так же разработку усложняет тот факт, что переиспользование (reusability) класса практически невозможно. Поэтому хорошим шагом было бы, во-первых, вынести отслеживание того, является ли экземпляр класса единственным, из класса куда-либо во вне, а во-вторых, сделать так, чтобы у класса, в зависимости от контекста, появилась возможность перестать быть Singleton’ом, что позволило бы использовать его в разных ситуациях, в зависимости от необходимости (т.е. с одним экземпляром, с неограниченным количество экземпляров, с ограниченным набором экземпляров и так далее).

Тестирование

Один из главных минусов паттерна «Одиночка» — он сильно затрудняет юнит-тестирование. «Одиночка» привносит в программу глобальное состояние, поэтому вы не можете просто взять и изолировать классы, которые полагаются на Singleton. Поэтому, если вы хотите протестировать какой-то класс, то вы обязаны вместе с ним тестировать и Singleton, но это ещё полбеды. Состояние «Одиночки» может меняться, что порождает следующие проблемы:

На эту тему есть отличный доклад с «Google Tech Talks»:

Скрытые зависимости

Обычно, если классу нужно что-то для работы, это сразу понятно из его методов и конструкторов. Когда очевидно, какие зависимости есть у класса, гораздо проще их предоставить. Более того, в таком случае вы можете использовать вместо реально необходимых зависимостей заглушки для тестирования. Если же класс использует Singleton, это может быть совершенно не очевидно. Всё становится гораздо хуже, если экземпляру класса для работы необходима определённая инициализация (например, вызов метода init(. ) или вроде того). Ещё хуже, если у вас существует несколько Singleton’ов, которые должны быть созданы и инициализированы в определённом порядке.

Загрузчик класса

Если говорить о Java, то обеспечение существования лишь одного экземпляра класса, которое так необходимо для Singleton, становится всё сложнее. Проблема в том, что классическая реализация не проверяет, существует ли один экземпляр на JVM, он лишь удостоверяется, что существует один экземпляр на classloader. Если вы пишете небольшое клиентское приложение, в котором используется лишь один classloader, то никаких проблем не возникнет. Однако если вы используете несколько загрузчиков класса или ваше приложение должно работать на сервере (где может быть запущено несколько экземпляров приложения в разных загрузчиках классов), то всё становится очень печально.

Десериализация

Ещё один интересный момент заключается в том, что на самом деле стандартная реализация Singleton не запрещает создавать новые объекты. Она запрещает создавать новые объекты через конструктор. А ведь существуют и другие способы создать экземпляр класса, и один из них — сериализация и десериализация. Полной защиты от намеренного создания второго экземпляра Singleton’а можно добиться только с помощью использования enum’а с единственным состоянием, но это — неоправданное злоупотребление возможностями языка, ведь очевидно, что enum был придуман не для этого.

Потоконебезопасность

Один из популярных вариантов реализации Singleton содержит ленивую инициализацию. Это значит, что объект класса создаётся не в самом начале, а лишь когда будет получено первое обращение к нему. Добиться этого совсем не сложно:

Однако здесь начинаются проблемы с потоками, которые могут создавать несколько различных объектов. Происходит это примерно так:

Есть два способа решить эту проблему. Первый — пометить как synchronised не весь метод, а только блок, где создаётся объект:

Второй путь — использовать паттерн «Lazy Initialization Holder». Это решение основано на том, что вложенные классы не инициализируются до первого их использования (как раз то, что нам нужно):

Рефлексия

Мы запрещаем создавать несколько экземпляров класса, помечая конструктор приватным. Тем не менее, используя рефлексию, можно без особого труда изменить видимость конструктора с private на public прямо во время исполнения:

Конечно, если вы используете Singleton только в своём приложении, переживать не о чем. А вот если вы разрабатываете модуль, который затем будет использоваться в сторонних приложениях, то из-за этого могут возникнуть проблемы. Какие именно, зависит от того, что делает ваш «Одиночка» — это могут быть как и риски, связанные с безопасностью, так и просто непредсказуемое поведение модуля.

Заключение

Несмотря на то, что паттерн Singleton очень известный и популярный, у него есть множество серьёзных недостатков. Чем дальше, тем больше этих недостатков выявляется, и оригинальные паттерны из книги GOF «Design Patterns» часто сегодня считаются антипаттернами. Тем не менее, сама идея иметь лишь один объект на класс по-прежнему имеет смысл, но достаточно сложно реализовать ее правильно.

Источник

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

Что такое синглтон?

Дает гарантию, что у класса будет всего один экземпляр класса.

Предоставляет глобальную точку доступа к экземпляру данного класса.

Приватный конструктор. Ограничивает возможность создания объектов класса за пределами самого класса.

Варианты реализации

Ленивая инициализация: когда класс загружается во время работы приложения именно тогда, когда он нужен.

Простота и прозрачность кода: метрика, конечно, субъективная, но важная.

Потокобезопасность: корректная работа в многопоточной среде.

Высокая производительность в многопоточной среде: потоки блокируют друг друга минимально, либо вообще не блокируют при совместном доступе к ресурсу.

Не ленивая инициализация: когда класс загружается при старте приложения, независимо от того, нужен он или нет (парадокс, в мире IT лучше быть лентяем)

Сложность и плохая читаемость кода. Метрика также субъективная. Будем считать, что если кровь пошла из глаз, реализация так себе.

Отсутствие потокобезопасности. Иными словами, “потокоопасность”. Некорректная работа в многопоточной среде.

Низкая производительность в многопоточной среде: потоки блокируют друг друга все время либо часто, при совместном доступе к ресурсу.

Самая простая реализация. Плюсы:

    Простота и прозрачность кода

    Высокая производительность в многопоточной среде

    Реализация интересна. Мы можем инициализироваться лениво, но утратили потокобезопасность. Не беда: в реализации номер три мы все синхронизируем.

    Низкая производительность в многопоточной среде

    Отлично! В реализации номер три мы вернули потокобезопасность! Правда, медленную… Теперь метод getInstance синхронизирован, и входить в него можно только по одному. На самом деле нам нужно синхронизировать не весь метод, а лишь ту его часть, в которой мы инициализируем новый объект класса. Но мы не можем просто обернуть в synchronized блок часть, отвечающую за создание нового объекта: это не обеспечит потокобезопасность. Все немного сложнее. Правильный способ синхронизации представлен ниже:

    Double Checked Locking

    Высокая производительность в многопоточной среде

    Не поддерживается на версиях Java ниже 1.5 (в версии 1.5 исправили работу ключевого слова volatile)

    Class Holder Singleton

    Высокая производительность в многопоточной среде.

    Реализация практически идеальная. И ленивая, и потокобезопасная, и быстрая. Но есть нюанс, описанный в минусе. Сравнительная таблица различных реализаций паттерна Singleton:

    РеализацияЛенивая инициализацияПотокобезопасностьСкорость работы при многопоточностиКогда использовать?
    Simple Solution+БыстроНикогда. Либо когда не важна ленивая инициализация. Но лучше никогда.
    Lazy Initialization+НеприменимоВсегда, когда не нужна многопоточность
    Synchronized Accessor++МедленноНикогда. Либо когда скорость работы при многопоточности не имеет значения. Но лучше никогда
    Double Checked Locking++БыстроВ редких случаях, когда нужно обрабатывать исключения при создании синглтона. (когда неприменим Class Holder Singleton)
    Class Holder Singleton++БыстроВсегда, когда нужна многопоточность и есть гарантия, что объект синглтон класса будет создан без проблем.

    Плюсы и минусы паттерна Singleton

    Дает гарантию, что у класса будет всего один экземпляр класса.

    Предоставляет глобальную точку доступа к экземпляру данного класса.

    Синглтон нарушает SRP (Single Responsibility Principle) — класс синглтона, помимо непосредственных обязанностей, занимается еще и контролированием количества своих экземпляров.

    Зависимость обычного класса или метода от синглтона не видна в публичном контракте класса.

    Глобальные переменные это плохо. Синглтон превращается в итоге в одну здоровенную глобальную переменную.

    Наличие синглтона снижает тестируемость приложения в целом и классов, которые используют синглтон, в частности.

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

    Источник

    Использование Singleton в Unity3D

    Вступление

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

    В данной статье мы постарались не только описать такой подход к организации кода, как Singleton (в народе называемый паттерном проектирования), но и рассмотреть наиболее комфортные и правильные подходы к обработке событий и поговорить об удобности кода в целом.

    Итак, в этой статье мы затронем следующие моменты:

    Как работает Singleton

    Прежде чем начать разбираться в схеме работы паттерна Singleton, необходимо понять что это. Singleton (Синглтон) — некий менеджер, через который производится управление игровыми скриптами. Как правило, синглтоны сохраняются от сцены к сцене без повторной реинициализации (наподобие глобального объекта).

    На простейшем примере работу Singleton можно объяснить следующим образом:
    В игре присутствуют глобальные объекты (менеджеры), которые будут находиться в игре всегда и могут быть доступны из любого скрипта, что может быть полезно для создания классов управления музыкой, сетевыми функциями, локализацией и всем тем, что используется в единственном экземпляре. Помимо менеджеров в игре будут использоваться и множественные объекты: интерфейсы, игровые персонажи и объекты игрового мира. Все эти объекты будут плотно взаимодействовать с нашими менеджерами для достижения конечной цели.

    Рассмотрим для примера организацию работы в мобильной игре:

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

    На схеме ниже мы изобразили схему работы на примере мобильной пошаговой онлайн-игры:
    Для чего нужен синглтон. Смотреть фото Для чего нужен синглтон. Смотреть картинку Для чего нужен синглтон. Картинка про Для чего нужен синглтон. Фото Для чего нужен синглтон
    Чтобы иметь полную картину, рассмотрим архитектуру этой игры. В данном случае помимо объектов Singleton у нас будут присутствовать следующие элементы:

    Реализация Singleton в Unity3D

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

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

    На примере выше мы создали основу для одного из игровых менеджеров (в нашем случае это менеджер Audio). Не обязательно проводить инициализацию через метод Start(). Вы также можете использовать для этого метод Awake(), чтобы ваш объект был готов еще до старта сцены.

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

    Итак, готово. Теперь наш менеджер аудио умеет загружать и сохранять настройки звуков и музыки. Теперь встает следующий вопрос о том, как мы можем это использовать. На примере ниже, мы продемонстрировали простой пример взаимодействия с менеджером:

    На примере выше мы создали компонент, позволяющий нам автоматически включать/отключать AudioSource на объекте на основе статичных полей music и sounds в нашем менеджере.

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

    Рассмотрим наш класс Boostrap-а:

    Теперь мы можем использовать Boostrap и добавлять в него новые префабы менеджеров без необходимости их размещения на каждой сцене игры.

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

    В нашем случае после запроса к серверу мы будем выгружать полученные данные в модели и обрабатывать их данные. Рассмотрим простейшую модель данных:

    На примере выше у нас изображена модель данных, которая будет служить для обработки базовых статусов, получаемых с сервера в формате JSON. Таким образом, когда мы обращаемся к нашему игровому серверу мы получаем 2 вида ответа:

    При успешном обращении мы получаем ответ следующего вида:

    А при ошибке мы получаем ответ следующего вида:

    Таким образом мы можем парсить ответ сервера при помощи JSON десериализации и нашей модели данных:

    Контроллеры будут служить нам для работы множественных объектов в игре (к примеру, противники в игре, либо контроллер игрока). Контроллеры создаются самым обычным способом и цепляются на объекты в игре в качестве компонентов.

    Пример простого контроллера игрока:

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

    Рассмотрим данную строку:

    Здесь мы видим, что идет сравнение индекса в цикле с идентификатором волос в модели данных игрока. Данная модель представлена в экземпляре объекта менеджера сети (NetworkManager), где был инициализирован объект для работы с авторизацией (auth), внутри которого размещены модели данных (player_data => profile_data => body).

    Взаимодействие с Singleton

    Для взаимодействия с менеджерами мы будем использовать либо экземпляр объекта (instance), либо прямое обращение для статических параметров.

    Пример работы с instance:

    На примере выше мы использовали свойство instance для получения данных о волосах игрока в менеджере NetworkManager.

    Пример прямого взаимодействия со static-параметрами:

    На примере выше мы обратились напрямую к статичному свойству sounds в менеджере AudioManager.

    О плюсах и минусах Singleton

    + Нет необходимости постоянной настройки и описаний полей скриптов в инспекторе
    + К менеджерам можно обращаться через свойство instance
    + Удобный рефакторинг кода
    + Компактность кода

    — Сильная зависимость кода
    — Доступ только к скриптам-менеджерам в единственном экземпляре

    Немного практических примеров

    Использование делегатов
    Мы можем сделать наш код более отзывчивым, добавив в менеджеры функции-делегаты. Таким образом для каждой функции может быть создан метод обратного вызова (callback).

    Рассмотрим данный пример:

    На простом примере выше мы создали метод, который вызываем функцию success, если параметр number был меньше 10 и функцию error, когда параметр был больше или равен 10 соответственно.

    Использовать данный метод можно следующим способом:

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

    Делегаты в связке с Coroutine в Singleton

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

    Рассмотрим данный пример NetworkManager-а:

    Теперь мы можем использовать это по назначению:

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

    Заключение

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

    Источник

    Для чего нужен singleton?

    Оценить 1 комментарий

    Для чего нужен синглтон. Смотреть фото Для чего нужен синглтон. Смотреть картинку Для чего нужен синглтон. Картинка про Для чего нужен синглтон. Фото Для чего нужен синглтон

    В общем и целом: синглтон как таковой не нужен вообще. Это бред придуманный программистами в пьяном угаре.

    Тем же раскладом вы можете создать файл app.class.php где написать
    Class App <>
    return new App;

    и когда подключаете файл в файле сборки сделать:

    $app = require_once app.class.php;

    Как сказал бы любой яваскриптер пхп-шнику «ребят, мне бы ваши проблемы»

    Для чего нужен синглтон. Смотреть фото Для чего нужен синглтон. Смотреть картинку Для чего нужен синглтон. Картинка про Для чего нужен синглтон. Фото Для чего нужен синглтон

    Для чего нужен синглтон. Смотреть фото Для чего нужен синглтон. Смотреть картинку Для чего нужен синглтон. Картинка про Для чего нужен синглтон. Фото Для чего нужен синглтон

    Для чего нужен синглтон. Смотреть фото Для чего нужен синглтон. Смотреть картинку Для чего нужен синглтон. Картинка про Для чего нужен синглтон. Фото Для чего нужен синглтон

    Для чего нужен синглтон. Смотреть фото Для чего нужен синглтон. Смотреть картинку Для чего нужен синглтон. Картинка про Для чего нужен синглтон. Фото Для чего нужен синглтон

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

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

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

    В качестве альтернативы синглтонам пропагандируются два других подхода: Registry / Service locator и Dependency injection.
    К слову, все альтернативы столь же активно и все так же незаслуженно обзываются антипаттернами. 🙂

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

    Источник

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *