Я использую игровой движок cocos2d-x для создания игр. Двигатель уже использует много синглетонов. Если кто-то использовал это, то они должны быть знакомы с некоторыми из них:
Director
SimpleAudioEngine
SpriteFrameCache
TextureCache
EventDispatcher (was)
ArmatureDataManager
FileUtils
UserDefault
и многое другое с общим количеством около 16 классов. Подобный список вы можете найти на этой странице: Объекты-одиночки в Cocos2d-html5 v3.0 Но когда я хочу написать свою игру, мне нужно гораздо больше синглетонов:
PlayerData (score, lives, ...)
PlayerProgress (passed levels, stars)
LevelData (parameters per levels and level packs)
SocialConnection (Facebook and Twitter login, share, friend list, ...)
GameData (you may obtain some data from server to configure the game)
IAP (for in purchases)
Ads (for showing ads)
Analytics (for collecting some analytics)
EntityComponentSystemManager (mananges entity creation and manipulation)
Box2dManager (manages the physics world)
.....
Почему я думаю, что они должны быть одиночками? Потому что они понадобятся мне в самых разных местах в моей игре, и общий доступ был бы очень удобен. Другими словами, я не знаю, что их создавать и передавать указатели на всю мою архитектуру, так как это будет очень сложно. Также это такие вещи, которые мне нужны только один. В любом случае мне понадобится несколько, я тоже могу использовать шаблон Multiton. Но хуже всего то, что синглтон является наиболее критично из-за:
- bad testability
- no inheritance available
- no lifetime control
- no explicit dependency chain
- global access (the same as global variables, actually)
- ....
Вы можете найти некоторые мысли здесь: https://stackoverflow.com/questions/137975/what-is-so-bad-about-singletons и https://stackoverflow.com/questions/4074154/when-should-the-singleton -pattern-не-быть использованы, кроме того, очевидно , заместитель
Так что, думаю, я что-то не так делаю. Я думаю, что мой код пахнет . :) Мне интересно, как более опытные разработчики игр решают эту архитектурную проблему? Я хочу проверить, может быть, в разработке игр все еще нормально иметь более 30 синглетонов , считая те, которые уже есть в игровом движке.
Я думал использовать Singleton-Facade, который будет иметь экземпляры всех этих классов, которые мне нужны, но каждый из них уже не будет синглетоном. Это устранит множество проблем, и у меня будет только один синглтон, который будет сам Фасад. Но в этом случае у меня будет другая проблема дизайна. Фасад станет БОГОВЫМ ОБЪЕКТОМ. Я думаю, что это тоже пахнет . Поэтому я не могу найти хорошее дизайнерское решение для этой ситуации. Пожалуйста посоветуй.
источник
Ответы:
Я инициализирую свои сервисы в своем главном классе приложения и затем передаю их как указатели на все, что нужно, чтобы использовать их через конструкторы или функции. Это полезно по двум причинам.
Во-первых, порядок инициализации и очистки прост и понятен. Невозможно случайно инициализировать один сервис где-то еще, как вы можете с помощью singleton.
Во-вторых, понятно, кто от чего зависит. Я легко вижу, какие классы нуждаются в каких услугах, просто из объявления класса.
Вы можете подумать, что раздражать, когда все эти объекты обходятся, но если система спроектирована хорошо, это совсем не плохо и делает вещи намного понятнее (во всяком случае, для меня).
источник
Я не буду говорить о зле, стоящем за синглетами, потому что Интернет может сделать это лучше меня.
В своих играх я использую шаблон Service Locator, чтобы избежать множества одиночных / управляющих.
Концепция довольно проста. У вас есть только один синглтон, который действует как единственный интерфейс для достижения того, что вы использовали в качестве синглтона. Вместо нескольких синглтонов теперь у вас есть только один.
Звонки как:
Выглядит так, используя шаблон Service Locator:
Как видите, каждый синглтон содержится в локаторе сервиса.
Чтобы получить более подробное объяснение, я предлагаю вам прочитать эту страницу о том, как использовать шаблон локатора .
[ РЕДАКТИРОВАТЬ ]: как указано в комментариях, сайт gameprogrammingpatterns.com также содержит очень интересный раздел о синглтоне .
Я надеюсь, что это помогает.
источник
Читая все эти ответы, комментарии и статьи, особенно эти две блестящие статьи,
в конце концов я пришел к следующему выводу, который является своего рода ответом на мой собственный вопрос. Лучший подход - не лениться и напрямую передавать зависимости. Это явное, тестируемое, глобальный доступ отсутствует, может указывать на неправильный дизайн, если вам нужно очень глубоко передавать делегатов, во многих местах и так далее. Но в программировании один размер не подходит всем. Таким образом, все еще есть случаи, когда не удобно передавать их напрямую. Например, в случае, если мы создаем игровую среду (шаблон кода, который будет повторно использоваться в качестве базового кода для разработки различных видов игр) и включаем в него множество сервисов. Здесь мы не знаем, насколько глубоко можно пройти каждую услугу, и сколько мест она будет необходима. Это зависит от конкретной игры. Так что же нам делать в подобных случаях? Мы используем шаблон проектирования Service Locator вместо Singleton,
Также следует учитывать, что этот паттерн использовался в Unity, LibGDX, XNA. Это не преимущество, но это своего рода доказательство удобства использования шаблона. Учтите, что эти движки разрабатывались многими умными разработчиками долгое время, и на этапе эволюции движка они все еще не находили лучшего решения.
В заключение, я думаю, что Service Locator может быть очень полезен для сценариев, описанных в моем вопросе, но все же его следует избегать, если это возможно. Как правило, используйте его, если нужно.
РЕДАКТИРОВАТЬ: После года работы и использования прямого DI и проблем с огромными конструкторами и большим количеством делегаций, я провел больше исследований и нашел хорошую статью, в которой рассказывается о плюсах и минусах методов DI и Service Locator. Ссылаясь на эту статью ( http://www.martinfowler.com/articles/injection.html ) Мартина Фаулера, объясняющую, когда на самом деле локаторы служб плохие:
Но в любом случае, если вы хотите, чтобы мы DI впервые, вы должны прочитать эту статью http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/ (указано @megadan) , уделяя очень пристальное внимание Закону Деметры (LoD) или принципу наименьшего знания .
источник
Нередки случаи, когда части кодовой базы считаются краеугольными объектами или базовым классом, но это не оправдывает то, что его жизненный цикл определяется как Singleton.
Программисты часто полагаются на шаблон Singleton как средство удобства и чистой лени, вместо того, чтобы использовать альтернативный подход и быть немного более многословными и навязывающими объектные отношения друг с другом в явном поместье.
Так что спросите себя, что легче поддерживать.
Если вы похожи на меня, я предпочитаю иметь возможность открывать заголовочный файл и кратко просматривать аргументы конструктора и публичные методы, чтобы увидеть, какие зависимости требуются объекту, вместо того, чтобы проверять сотни строк кода в файле реализации.
Если вы сделали объект синглтоном и позже понимаете, что должны быть способны поддерживать несколько экземпляров указанного объекта, представьте себе, какие существенные изменения необходимы, если этот класс был чем-то, что вы довольно активно использовали в своей кодовой базе. Лучшая альтернатива - сделать зависимости явными, даже если объект выделяется только один раз, и если возникает необходимость поддержки нескольких экземпляров, вы можете безопасно сделать это без таких проблем.
Как уже отмечали другие, использование шаблона Singleton также скрывает связь, что часто означает плохую конструкцию и высокую когезию между модулями. Такая сплоченность обычно нежелательна и приводит к хрупкому коду, который трудно поддерживать.
источник
Синглтон - известная модель, но хорошо знать цели, которым он служит, а также его плюсы и минусы.
Это действительно имеет смысл, если нет никаких отношений вообще.
Если вы можете обрабатывать свой компонент с совершенно другим объектом (без сильной зависимости) и ожидать такого же поведения, то синглтон может быть хорошим выбором.
С другой стороны, если вам нужна небольшая функциональность , используемая только в определенное время, вы должны предпочесть передачу ссылок .
Как вы упомянули, синглтоны не имеют контроля над жизнью. Но преимущество в том, что они имеют ленивую инициализацию.
Если вы не вызовете этот синглтон при жизни приложения, он не будет создан. Но я сомневаюсь, что вы строите синглтоны, а не используете их.
Вы должны увидеть синглтон как сервис вместо компонента .
Синглтоны работают, они никогда не ломали ни одного приложения. Но их недостатки (четыре, которые вы дали) являются причинами, по которым вы не сделаете один.
источник
Незнакомость не является причиной, по которой следует избегать синглетонов.
Есть много веских причин, почему Singleton необходим или неизбежен. В игровых фреймворках часто используются синглтоны, потому что это неизбежное последствие наличия всего одного аппаратного обеспечения с сохранением состояния. Нет смысла когда-либо хотеть управлять этим оборудованием с несколькими экземплярами соответствующих им обработчиков. Графическая поверхность - это внешнее аппаратное обеспечение с сохранением состояния, и случайная инициализация второй копии графической подсистемы является не чем иным, как катастрофой, так как теперь две графические подсистемы будут сражаться друг с другом за то, кто может рисовать и когда, неконтролируемо перезаписывая друг друга. Аналогично с системой очередей событий они будут бороться за то, кто получает события мыши недетерминированным способом. Когда речь идет о внешнем оборудовании с состоянием, в котором есть только одно из них, синглтон неизбежно предотвращает конфликты.
Другое место, где Синглтон чувствителен, это менеджеры Cache. Кеши - это особый случай. Их не следует считать Синглтоном, даже если они используют те же приемы, что и Синглтоны, чтобы остаться в живых и жить вечно. Службы кэширования являются прозрачной службой, они не должны изменять поведение программы, поэтому, если вы замените службу кэша нулевым, программа все равно должна работать, за исключением того, что она работает медленнее. Однако главная причина, по которой менеджеры кэша являются исключением для синглтона, заключается в том, что нет смысла отключать службу кэширования перед закрытием самого приложения, поскольку это также отбрасывает кэшированные объекты, что лишает смысла иметь его как синглтон.
Это веские причины, по которым эти услуги являются синглетонами. Однако ни одна из веских причин, по которой у вас есть синглтоны, не подходит для перечисленных вами классов.
Это не повод для одиноких. Это причины для глобалов. Это также признак плохого дизайна, если вам нужно обойтись вокруг разных систем. Необходимость передавать вещи указывает на высокое сцепление, которое может быть предотвращено хорошей конструкцией OO.
Просто взглянув на список классов в вашем посте, которые, по вашему мнению, должны быть синглетонами, я могу сказать, что половина из них действительно не должна быть синглетонами, а другая половина кажется, что их вообще не должно быть. То, что вам нужно передавать объекты, кажется, связано с отсутствием надлежащей инкапсуляции, а не с реальными хорошими вариантами использования для синглетонов.
Давайте посмотрим на ваши классы по крупицам:
Просто из названий классов я бы сказал, что эти классы пахнут как антипаттерн Anemic Domain Model . Анемичные объекты обычно приводят к большому количеству данных, которые необходимо передать, что увеличивает связь и делает остальную часть кода громоздкой. Кроме того, анемичный класс скрывает тот факт, что вы, вероятно, все еще мыслите процедурно, а не используете ориентацию объекта для инкапсуляции деталей.
Почему эти классы должны быть одиночками? Мне кажется, что это должны быть недолговечные классы, которые следует вызывать, когда они необходимы, и деконструировать, когда пользователь завершает покупку или когда рекламу больше не нужно показывать.
Другими словами, конструктор и сервисный уровень? Почему этот класс не имеет четких границ и цели даже там, в первую очередь?
Почему прогресс игрока отделен от класса Player? Класс Player должен знать, как отслеживать собственный прогресс, если вы хотите реализовать отслеживание прогресса в классе, отличном от класса Player для разделения ответственности, то PlayerProgress должен быть позади Player.
Я не могу комментировать дальше, не зная, что на самом деле делает этот класс, но ясно одно - этот класс плохо назван.
Похоже, что это единственные классы, где Singleton может быть разумным, потому что объекты социальных связей на самом деле не являются вашими объектами. Социальные связи - это просто тень внешних объектов, которые живут в удаленном сервисе. Другими словами, это своего рода кеш. Просто убедитесь, что вы четко отделяете кеширующую часть от служебной части внешней службы, потому что последняя часть не должна быть одиночной.
Один из способов избежать передачи экземпляров этих классов - использовать передачу сообщений. Вместо прямого вызова экземпляров этих классов вместо этого вы отправляете сообщение, адресованное службе Analytic и SocialConnection, асинхронно, и эти службы подписываются на получение этих сообщений и действуют на них. Поскольку очередь событий уже является одноэлементной, трамплинируя вызов, вы избегаете необходимости передавать фактический экземпляр при взаимодействии с одноэлементным.
источник
Синглтоны для меня ВСЕГДА плохие.
В своей архитектуре я создал коллекцию GameServices, которой управляет игровой класс. Я могу искать добавить и удалить из этой коллекции по мере необходимости.
Говоря о том, что что-то находится в глобальном масштабе, вы в основном говорите «этот хтинг - бог, и у него нет хозяина» в приведенных выше примерах я бы сказал, что большинство из них являются либо вспомогательными классами, либо GameServices, и в этом случае за них будет отвечать игра.
Но это только мой дизайн в моем двигателе, ваша ситуация может быть другой.
Проще говоря ... Единственный настоящий синглтон - это игра, поскольку она владеет каждой частью кода.
Это ответ основан на субъективной природе вопроса и основан исключительно на моем мнении, он может быть не верным для всех разработчиков.
Изменить: по запросу ...
Хорошо, это из моей головы, так как в данный момент у меня нет кода передо мной, но, по сути, в основе любой игры лежит класс Game, который действительно является одиночным ... так и должно быть!
Поэтому я добавил следующее ...
Теперь у меня также есть системный класс (статический), который содержит все мои синглтоны из всей программы (содержит очень мало, опций игры и конфигурации и тому подобное) ...
И использование ...
Из любого места я могу сделать что-то вроде
Этот подход означает, что моя игра «владеет» вещами, которые обычно считаются «одноэлементными», и я могу расширить базовый класс GameService так, как мне хочется. У меня есть интерфейсы, такие как IUpdateable, которые моя игра проверяет и вызывает в любых службах, которые реализуют ее, как это необходимо для конкретных ситуаций.
У меня также есть службы, которые наследуют / расширяют другие службы для более конкретных сценариев сцены.
Например ...
У меня есть физический сервис, который обрабатывает мои физические симуляции, но в зависимости от физической симуляции сцены может потребоваться немного другое поведение.
источник
var tService = Sys.Game.getService<T>(); tService.Something();
вместо пропускаgetService
части и прямого доступа к ней?Существенным компонентом уничтожения синглтона, который часто игнорируют противники синглтона, является добавление дополнительной логики для решения гораздо более сложного состояния, которое объекты инкапсулируют, когда синглтоны уступают значениям экземпляра. В самом деле, даже определение состояния объекта после замены синглтона переменной экземпляра может быть затруднено, но программисты, которые заменяют синглеты переменными экземпляра, должны быть готовы к этому.
Сравните, например, Java
HashMap
с .NETDictionary
. Оба довольно похожи, но у них есть ключевое различие: JavaHashMap
жестко запрограммирован для использованияequals
иhashCode
(он эффективно использует одноэлементный компаратор), в то время как .NETDictionary
может принимать экземплярIEqualityComparer<T>
в качестве параметра конструктора. Стоимость выполнения храненияIEqualityComparer
минимальны, но сложность, которую он представляет, не является.Поскольку каждый
Dictionary
экземпляр инкапсулируетIEqualityComparer
, его состояние - это не просто отображение между ключами, которые он фактически содержит, и их ассоциированными значениями, а вместо этого отображение между наборами эквивалентности, определенными ключами и компаратором, и их связанными значениями. Если два экземпляраd1
иd2
оDictionary
ссылках придерживающихся жеIEqualityComparer
, можно будет производить новый экземплярd3
такой , что для любогоx
,d3.ContainsKey(x)
будет равноd1.ContainsKey(x) || d2.ContainsKey(x)
. Если, однако,d1
иd2
может содержать ссылки на различные произвольные компараторы равенства, не будет вообще никакого способа объединить их, чтобы убедиться , что условие выполнено.Добавление переменных экземпляра в попытке устранить синглтоны, но неспособность должным образом учесть возможные новые состояния, которые могут подразумеваться этими переменными экземпляра, может создать код, который в итоге окажется более хрупким, чем это было бы с одиночным кодом. Если у каждого экземпляра объекта есть отдельное поле, но вещи будут работать неправильно, если все они не идентифицируют один и тот же экземпляр объекта, то все новые купленные поля - это увеличенное число причин, по которым все может пойти не так.
источник