C ++ Singleton Design альтернативные шаблоны - PullRequest
0 голосов
/ 30 декабря 2018

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

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

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

Варианты использования следующие:

  • глобально доступный регистратор.
  • менеджер рендеринга OpenGL.
  • доступ к файловой системе.
  • доступ к сети.
  • и т. Д.

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

Теперь из того, что я могу сказать, более рекомендуется избегать синглетонов, поэтомудавайте посмотрим, как мы можем это сделать.Во многих статьях просто говорится, чтобы создать экземпляр вверху и передать его в качестве параметра в любое место, где это необходимо.Хотя я согласен с тем, что это технически выполнимо, возникает вопрос: как управлять потенциально огромным количеством параметров?Хорошо, что приходит на ум, оборачивая различные экземпляры в своего рода «контекстный» объект и передавая его, затем делая что-то вроде context->log("Hello World").Теперь убедитесь, что это не так уж и плохо, но что, если у вас есть своего рода фреймворк, подобный так:

game_loop(ctx)
   ->update_entities(ctx)
        ->on_preupdate(ctx)
             ->run_something(ctx)
                 ->only use ctx->log() in some freak edge case in this function.
        ->on_update(ctx)
            ->whatever(ctx)
                 ->ctx->networksend(stuff)
   ->update_physics(ctx)
        ->ctx->networksend(stuff)
        //maybe ctx never uses log here.

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

Я чувствую, что приведенный выше пример больше подходит для глобально доступного синглтона, но я должен признать, что я исходил из C # / Java / JS-фона, который может окрасить мой взгляд.Я хочу перенять образ мыслей / лучшие практики программиста на C ++, но, как я уже сказал, я не могу найти прямой ответ.Я также заметил, что в статьях, в которых предлагается просто передать «синглтон» в качестве параметра, приводятся лишь очень упрощенные варианты использования, при которых любой согласится с тем, что лучшим вариантом будет использование параметра.

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

Я тоже:

  1. Придерживайтесьиспользование синглетонов для этих случаев использования независимо от того, как люди говорят «зло / зло».
  2. Оберните все в объект контекста и передайте его буквально везде.(кажется довольно грубым ИМО, но если это «более приемлемый / лучший» способ сделать это, пусть будет так).
  3. Что-то совершенно другое.(Действительно потерянный относительно того, что это могло бы быть.)

Если вариант 1, с точки зрения производительности, я должен переключиться на использование функций пространства имен и скрытие «частных» переменных / функций в анонимных пространствах имен, таких какбольшинство людей делают в C?(Я предполагаю, что произойдет небольшое повышение производительности, но потом я застряну с необходимостью вызывать метод "init" и "destroy" для некоторых из них, вместо того, чтобы просто позволить конструктору / деструкторусделать это для меня, все еще может стоить?)

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

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

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

Я читал, что Service Locator также является своего рода анти-паттерном, в котором говорится, что многие из найденных мною примеров реализовали его с использованием статики и / или как синглтона, возможно, используя его, как я описал, удаляет аспектычто вызывает анти-паттерн?

1 Ответ

0 голосов
/ 30 декабря 2018

Всякий раз, когда вы думаете, что хотите использовать Singleton, задайте себе следующий вопрос: почему необходимо обеспечить любой ценой, чтобы в любой момент времени не было более одного экземпляра этого класса?Потому что весь смысл паттерна синглтона состоит в том, чтобы убедиться, что никогда не может быть более одного экземпляра синглтона.Вот что такое термин «синглтон»: только один.Вот почему это называется паттерн Синглтон.Вот почему шаблон требует, чтобы конструктор был закрытым.Смысл паттерна Синглтона - , а не , и он никогда не должен был дать вам глобально доступный экземпляр чего-либо.Тот факт, что существует единственная точка доступа к единственному экземпляру, является лишь следствием шаблона Singleton.Это не цель, которую должен достичь образец Синглтона.Если вам нужен глобально доступный экземпляр чего-либо, используйте глобальную переменную.Это именно то, для чего предназначены глобальные переменные ...

Шаблон Singleton, вероятно, является единственным шаблоном проектирования, который чаще всего неправильно понимают, чем нет.Является ли внутренним аспектом самой концепции сетевого соединения то, что одновременно может быть только одно сетевое соединение, и мир придет к концу, если это ограничение когда-либо будет нарушено?Если ответ отрицательный, то нет никакого оправдания тому, чтобы сетевое соединение было когда-либо смоделировано как Singleton.Но не верьте мне на слово, убедитесь сами, проверив страницу 127 (если память хорошо работает) Шаблоны проектирования: элементы многоразового объектно-ориентированного программного обеспечения , где изначально был представлен шаблон Singleton… ?

Относительно вашего примера: если вам в конечном итоге приходится передавать огромное количество параметров в какое-то место, то это в первую очередь говорит вам об одном: в этом месте слишком много обязанностей.Этот факт не изменяется при использовании синглетонов.Использование Singletons просто запутывает этот факт, потому что вы не обязаны передавать все вещи через одну дверь в виде параметров, а просто получаете доступ к тому, что вы хотите, прямо повсюду.Но вы все еще получаете доступ к этим вещам.Таким образом, зависимости вашего куска кода одинаковы.Эти зависимости просто больше не выражаются явно на каком-то уровне интерфейса, а заползают в туманы.И вы никогда не узнаете заранее, от чего зависит определенный фрагмент кода, до того момента, как ваша сборка сломается после попытки убрать одну вещь, от которой зависит что-то другое.Обратите внимание, что эта проблема не относится к шаблону Singleton.Это касается любого вида глобальной сущности в целом…

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

Что касается производительности, учтите, что в игре у вас обычно есть несколько родительских объектов, каждый из которых управляет коллекциями маленьких дочерних объектов.Критически важные для производительности вещи, как правило, происходят в тех местах, где нужно что-то сделать со всеми дочерними объектами в такой коллекции.Относительные накладные расходы при первом получении самого родительского объекта, как правило, должны быть незначительными…

PS: Возможно, вы захотите взглянуть на шаблон Система компонентов сущности

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