Как я могу избежать много синглетонов в моей игровой архитектуре?

55

Я использую игровой движок 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, который будет иметь экземпляры всех этих классов, которые мне нужны, но каждый из них уже не будет синглетоном. Это устранит множество проблем, и у меня будет только один синглтон, который будет сам Фасад. Но в этом случае у меня будет другая проблема дизайна. Фасад станет БОГОВЫМ ОБЪЕКТОМ. Я думаю, что это тоже пахнет . Поэтому я не могу найти хорошее дизайнерское решение для этой ситуации. Пожалуйста посоветуй.

Нарек
источник
26
Большинство людей, спорящих против Singleton, похоже, не понимают, что работа, необходимая для распространения некоторых данных / функциональных возможностей по всему коду, может быть скорее источником ошибок, чем использовать Singleton. Таким образом, в основном они не сравниваются, просто отвергают ==> я подозреваю, что анти-синглтонизм - это осанка Теперь, если вы не можете, пойти на это, не оглядываясь назад. В конце концов, Cocos2d, кажется, довольно хорошо работает с 16
синглетонами
6
Напомним, что это вопрос о том, как выполнить определенную задачу. Это не вопрос, требующий обсуждения синглетонов за / против (что в любом случае было бы здесь не по теме). Кроме того, помните , что комментарии должны быть использованы для запроса разъяснений от спрашивающего, а не в качестве платформы для тангенциальной дискуссии о преимуществах и недостатках одиночек. Если вы хотите внести свой вклад, то правильный способ сделать это - дать законный ответ на вопрос , в котором вы можете умерить свое решение советом за или против одноэлементного шаблона.
Джош
Я предполагаю, что эти синглтоны тогда доступны для всех? Что было бы одной из причин, чтобы избежать этой модели
Питер - Восстановить Монику

Ответы:

41

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

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

Во-вторых, понятно, кто от чего зависит. Я легко вижу, какие классы нуждаются в каких услугах, просто из объявления класса.

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

megadan
источник
34
+1. Кроме того, «раздражение» необходимости передавать ссылки на системы вокруг помогает противодействовать ползучести зависимости; если вам нужно что-то передать « всему », это является хорошим признаком того, что у вас есть проблема плохой связи в вашем дизайне, и это намного более явно, чем при беспорядочном глобальном доступе ко всему отовсюду.
Джош
2
Это на самом деле не дает решения ... так что у вас есть класс вашей программы с кучей одноэлементных объектов в нем, и теперь на 10 уровнях в глубине кода, где-то в сцене нужен один из этих синглетонов ... как он передается?
Война
Wardy: Зачем им быть одинокими? Конечно, в основном вы можете передавать конкретный экземпляр, но что делает синглтон синглтоном, так это то, что вы НЕ МОЖЕТЕ использовать другой экземпляр. С помощью инъекции вы можете передать один экземпляр для одного объекта, а другой - для следующего. То, что вы не делаете это по какой-либо причине, не означает, что вы не можете.
parar
3
Они не одиночки. Это обычные объекты, как и все остальное с четко определенным владельцем, который управляет init / cleanup. Если у вас 10 уровней без доступа, возможно, у вас есть проблема с дизайном. Не все должны или должны иметь доступ к средствам визуализации, аудио и тому подобному. Этот способ заставляет задуматься о своем дизайне. В качестве дополнительного бонуса для тех, кто тестирует свой код (что ??), модульное тестирование также становится намного проще.
мегадан
2
Да, я думаю, что внедрение зависимостей (с или без фреймворка, такого как Spring) является идеальным решением для этого. Я нашел, что это отличная статья для тех, кто интересуется, как лучше структурировать код, чтобы не пропустить его везде: misko.hevery.com/2008/10/21/…
megadan
17

Я не буду говорить о зле, стоящем за синглетами, потому что Интернет может сделать это лучше меня.

В своих играх я использую шаблон Service Locator, чтобы избежать множества одиночных / управляющих.

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

Звонки как:

FlyingToasterManager.GetInstance().Toast();
SmokeAlarmManager.GetInstance().Start();

Выглядит так, используя шаблон Service Locator:

SvcLocator.FlyingToaster.Toast();
SvcLocator.SmokeAlarm.Start();

Как видите, каждый синглтон содержится в локаторе сервиса.

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

[ РЕДАКТИРОВАТЬ ]: как указано в комментариях, сайт gameprogrammingpatterns.com также содержит очень интересный раздел о синглтоне .

Я надеюсь, что это помогает.

lvictorino
источник
4
На сайте, на который ссылается gameprogrammingpatterns.com, также есть глава о синглетонах, которая кажется актуальной.
ajp15243
28
Локаторы сервисов - это просто синглтоны, которые переносят все обычные проблемы во время выполнения, а не во время компиляции. Тем не менее, вы предлагаете просто гигантский статический реестр, который почти лучше, но все же по сути представляет собой массу глобальных переменных. Это просто проблема плохой архитектуры, если многим уровням нужен доступ к одним и тем же объектам. Надлежащая инъекция зависимостей - это то, что нужно здесь, глобальные имена не называются иначе, чем нынешние глобальные.
Волхв
2
Можете ли вы рассказать о «правильном внедрении зависимости»?
Синий Волшебник
17
Это не очень помогает проблеме. По сути, это множество синглетонов, объединенных в один гигантский синглтон. Ни одна из лежащих в основе структурных проблем не устранена и даже не решена, код все еще воняет просто красивее.
ClassicThunder
4
@classicthunder Хотя это не решает все проблемы, это большое улучшение по сравнению с Singletons, потому что оно решает несколько проблем. В частности, код, который вы пишете, больше не связан с одним классом, а скорее интерфейсом / категорией классов. Теперь вы можете легко поменять местами различные реализации различных сервисов.
Джоккинг
12

Читая все эти ответы, комментарии и статьи, особенно эти две блестящие статьи,

в конце концов я пришел к следующему выводу, который является своего рода ответом на мой собственный вопрос. Лучший подход - не лениться и напрямую передавать зависимости. Это явное, тестируемое, глобальный доступ отсутствует, может указывать на неправильный дизайн, если вам нужно очень глубоко передавать делегатов, во многих местах и ​​так далее. Но в программировании один размер не подходит всем. Таким образом, все еще есть случаи, когда не удобно передавать их напрямую. Например, в случае, если мы создаем игровую среду (шаблон кода, который будет повторно использоваться в качестве базового кода для разработки различных видов игр) и включаем в него множество сервисов. Здесь мы не знаем, насколько глубоко можно пройти каждую услугу, и сколько мест она будет необходима. Это зависит от конкретной игры. Так что же нам делать в подобных случаях? Мы используем шаблон проектирования Service Locator вместо Singleton,

  1. Вы программируете не для реализаций, а для интерфейсов. Это очень важно с точки зрения принципов ООП. Во время выполнения вы можете изменить или даже отключить службу, используя шаблон Null Object. Это важно не только с точки зрения гибкости, но и напрямую помогает в тестировании. Вы можете отключить, макет, также вы можете использовать шаблон Декоратор, чтобы добавить некоторые журналы и другие функции, а также. Это очень важное свойство, которого у Синглтона нет.
  2. Еще одним огромным преимуществом является то, что вы можете контролировать срок службы объекта. В Singleton вы контролируете только время, когда объект будет порожден паттерном Lazy Initialization. Но как только объект будет создан, он будет жить до конца программы. Но в некоторых случаях вы хотели бы сменить услугу. Или даже выключи его. Особенно в играх эта гибкость очень важна.
  3. Он объединяет / объединяет несколько Singleton в один компактный API. Я думаю, что вы также можете использовать некоторые Фасады, такие как Сервисные локаторы, которые для одной операции могут использовать несколько сервисов одновременно.
  4. Вы можете утверждать, что у нас все еще есть глобальный доступ, который является основной проблемой проектирования синглтона. Я согласен, но нам понадобится только эта модель, и только если мы хотим получить глобальный доступ. Мы не должны жаловаться на глобальный доступ, если мы думаем, что внедрение прямой зависимости не подходит для нашей ситуации, и нам нужен глобальный доступ. Но даже для этой проблемы доступно улучшение (см. Вторую статью выше). Вы можете сделать все сервисы приватными и унаследовать от класса Service Locator все классы, которые, по вашему мнению, должны использовать сервисы. Это может значительно сузить доступ. И, пожалуйста, учтите, что в случае синглтона вы не можете использовать наследование из-за закрытого конструктора.

Также следует учитывать, что этот паттерн использовался в Unity, LibGDX, XNA. Это не преимущество, но это своего рода доказательство удобства использования шаблона. Учтите, что эти движки разрабатывались многими умными разработчиками долгое время, и на этапе эволюции движка они все еще не находили лучшего решения.

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

РЕДАКТИРОВАТЬ: После года работы и использования прямого DI и проблем с огромными конструкторами и большим количеством делегаций, я провел больше исследований и нашел хорошую статью, в которой рассказывается о плюсах и минусах методов DI и Service Locator. Ссылаясь на эту статью ( http://www.martinfowler.com/articles/injection.html ) Мартина Фаулера, объясняющую, когда на самом деле локаторы служб плохие:

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

Но в любом случае, если вы хотите, чтобы мы DI впервые, вы должны прочитать эту статью http://misko.hevery.com/2008/10/21/dependency-injection-myth-reference-passing/ (указано @megadan) , уделяя очень пристальное внимание Закону Деметры (LoD) или принципу наименьшего знания .

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

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

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

Так что спросите себя, что легче поддерживать.

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

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

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

Naros
источник
-1: Этот ответ неверно описывает шаблон синглтона, а все его аргументы описывают проблемы с плохой реализацией шаблона. Правильный синглтон - это интерфейс, и он позволяет избежать каждой описанной вами проблемы (включая переход к неединственности, то есть к сценарию, который GoF явно рассматривает). Если рефакторинг использования явной ссылки на объект затруднит рефакторинг, то будет труднее, а не меньше. Если синглтон каким-то образом вызывает БОЛЕЕ связывание кода, это просто было сделано неправильно.
Сет Бэттин
1

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

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

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

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

Вы должны увидеть синглтон как сервис вместо компонента .

Синглтоны работают, они никогда не ломали ни одного приложения. Но их недостатки (четыре, которые вы дали) являются причинами, по которым вы не сделаете один.

Crazyrems
источник
1

Двигатель уже использует много синглетонов. Если кто-то использовал это, то они должны быть знакомы с некоторыми из них:

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

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

Другое место, где Синглтон чувствителен, это менеджеры Cache. Кеши - это особый случай. Их не следует считать Синглтоном, даже если они используют те же приемы, что и Синглтоны, чтобы остаться в живых и жить вечно. Службы кэширования являются прозрачной службой, они не должны изменять поведение программы, поэтому, если вы замените службу кэша нулевым, программа все равно должна работать, за исключением того, что она работает медленнее. Однако главная причина, по которой менеджеры кэша являются исключением для синглтона, заключается в том, что нет смысла отключать службу кэширования перед закрытием самого приложения, поскольку это также отбрасывает кэшированные объекты, что лишает смысла иметь его как синглтон.

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

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

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

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

Давайте посмотрим на ваши классы по крупицам:


PlayerData (score, lives, ...)
LevelData (parameters per levels and level packs)
GameData (you may obtain some data from server to configure the game)

Просто из названий классов я бы сказал, что эти классы пахнут как антипаттерн Anemic Domain Model . Анемичные объекты обычно приводят к большому количеству данных, которые необходимо передать, что увеличивает связь и делает остальную часть кода громоздкой. Кроме того, анемичный класс скрывает тот факт, что вы, вероятно, все еще мыслите процедурно, а не используете ориентацию объекта для инкапсуляции деталей.


IAP (for in purchases)
Ads (for showing ads)

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


EntityComponentSystemManager (mananges entity creation and manipulation)

Другими словами, конструктор и сервисный уровень? Почему этот класс не имеет четких границ и цели даже там, в первую очередь?


PlayerProgress (passed levels, stars)

Почему прогресс игрока отделен от класса Player? Класс Player должен знать, как отслеживать собственный прогресс, если вы хотите реализовать отслеживание прогресса в классе, отличном от класса Player для разделения ответственности, то PlayerProgress должен быть позади Player.


Box2dManager (manages the physics world)

Я не могу комментировать дальше, не зная, что на самом деле делает этот класс, но ясно одно - этот класс плохо назван.


Analytics (for collecting some analytics)
SocialConnection (Facebook and Twitter login, share, friend list, ...)

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

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

Ли Райан
источник
-1

Синглтоны для меня ВСЕГДА плохие.

В своей архитектуре я создал коллекцию GameServices, которой управляет игровой класс. Я могу искать добавить и удалить из этой коллекции по мере необходимости.

Говоря о том, что что-то находится в глобальном масштабе, вы в основном говорите «этот хтинг - бог, и у него нет хозяина» в приведенных выше примерах я бы сказал, что большинство из них являются либо вспомогательными классами, либо GameServices, и в этом случае за них будет отвечать игра.

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

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

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

Изменить: по запросу ...

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

Поэтому я добавил следующее ...

class Game
{
   IList<GameService> Services;

   public T GetService<T>() where T : GameService
   {
       return Services.FirstOrDefatult(s => s is T);
   }
}

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

public static class Sys
{
    // only the main game assembly can set this (done by the game ctor)
    // anything can get
    public static Game Game { get; internal set; }
}

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

Из любого места я могу сделать что-то вроде

var tService = Sys.Game.getService<T>();
tService.Something();

Этот подход означает, что моя игра «владеет» вещами, которые обычно считаются «одноэлементными», и я могу расширить базовый класс GameService так, как мне хочется. У меня есть интерфейсы, такие как IUpdateable, которые моя игра проверяет и вызывает в любых службах, которые реализуют ее, как это необходимо для конкретных ситуаций.

У меня также есть службы, которые наследуют / расширяют другие службы для более конкретных сценариев сцены.

Например ...

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

война
источник
1
Не должны ли игровые сервисы создаваться только один раз? Если это так, то это просто шаблон синглтона, который не применяется на уровне класса, что хуже, чем правильно реализованный синглтон.
GameAlchemist
2
@ Осторожно, я не совсем понимаю, что ты сделал, но это звучит как Singleton-Facade, который я описал, и он должен стать объектом БОГА, верно?
Нарек
10
Это просто шаблон Singleton, реализованный на уровне игры. Поэтому в основном его самый интересный аспект - позволить вам притвориться, что вы не используете Singleton. Полезно, если ваш босс из церкви против шаблонов.
GameAlchemist
2
Я не уверен, насколько это эффективно отличается от использования Singletons. Разве нет единственной разницы в том, как конкретно вы получаете доступ к своей единой существующей услуге этого типа? Т.е. var tService = Sys.Game.getService<T>(); tService.Something();вместо пропуска getServiceчасти и прямого доступа к ней?
Кристиан,
1
@ Кристиан: Действительно, это именно то, что антипаттерн Service Locator. Очевидно, это сообщество все еще думает, что это рекомендуемый шаблон дизайна.
Волхв
-1

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

Сравните, например, Java HashMapс .NET Dictionary. Оба довольно похожи, но у них есть ключевое различие: Java HashMapжестко запрограммирован для использования equalsи hashCode(он эффективно использует одноэлементный компаратор), в то время как .NET Dictionaryможет принимать экземпляр IEqualityComparer<T>в качестве параметра конструктора. Стоимость выполнения храненияIEqualityComparer минимальны, но сложность, которую он представляет, не является.

Поскольку каждый Dictionaryэкземпляр инкапсулирует IEqualityComparer, его состояние - это не просто отображение между ключами, которые он фактически содержит, и их ассоциированными значениями, а вместо этого отображение между наборами эквивалентности, определенными ключами и компаратором, и их связанными значениями. Если два экземпляра d1и d2о Dictionaryссылках придерживающихся же IEqualityComparer, можно будет производить новый экземпляр d3такой , что для любого x, d3.ContainsKey(x)будет равно d1.ContainsKey(x) || d2.ContainsKey(x). Если, однако, d1и d2может содержать ссылки на различные произвольные компараторы равенства, не будет вообще никакого способа объединить их, чтобы убедиться , что условие выполнено.

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

Supercat
источник
1
Хотя это, безусловно, интересный аспект обсуждения, я не думаю, что он фактически затрагивает вопрос, фактически заданный ФП.
Джош
1
@JoshPetrie: комментарии к оригинальному сообщению (например, от Magus) предположили, что затраты времени выполнения переноса ссылок, чтобы избежать использования синглетонов, были невелики. Таким образом, я бы посчитал уместным смысловые затраты на то, чтобы каждый объект инкапсулировал ссылки для избежания одиночных разрядов. Следует избегать синглетонов в тех случаях, когда их можно избежать дешево и легко, но код, который явно не требует синглтона, но может сломаться, как только несколько экземпляров чего-либо существуют, хуже, чем код, использующий синглтон.
суперкат
2
Ответы не предназначены для адресации комментариев, они предназначены для непосредственного ответа на вопрос.
ClassicThunder
@ClassicThunder: Вам нравится редактирование лучше?
суперкат