Я перебиваюсь с одиночками? - PullRequest
9 голосов
/ 18 ноября 2011

Я довольно новичок в C ++ и пишу кроссплатформенный (настольный / мобильный) движок 2D-игр ... мой вопрос, правильно ли я использую синглтоны, и если нет, есть ли альтернатива?

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

VBOManager (Singleton)
Этот «менеджер» в основном отвечает за распределение и, конечно, «управление» виртуальными машинами, которые используются для хранения текстурного наложения и координат вершин. Я управляю «чтением / записью» через этот объект, чтобы я мог кэшировать данные, записанные в vbo другими объектами, и возвращать указатели (избегая хранения дублирующихся данных, таких как 500 спрайтов с тем же отображением и координатами вершин).

TextureManager (Singleton, не требует пояснений)

GLUtils (Singleton)
Я в значительной степени использую это для объединения общих вызовов GL, которые различаются в зависимости от текущей платформы, например, GL или GLES. Пример, функции GLU (libglu не работает на Android, поэтому у меня есть пользовательская реализация)

Графика (синглтон)
Используется для обработки таких вещей, как инициализация и изменение размеров окон, глобальное управление частотой кадров, инициализация области просмотра и т. Д.

InputManager (Singleton, не требует пояснений)

Во всяком случае, здесь я рассматриваю все это и начинаю чувствовать себя очень, очень грязно. Я чувствую, что, учитывая требования и функциональность данных объектов, это оправдано. Но, с другой стороны, я новичок, и что-то не в порядке, когда этот «шаблон» настолько распространен в моем коде. Если последнее действительно так, что может быть альтернативой?

Ответы [ 7 ]

7 голосов
/ 18 ноября 2011

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

  1. «объект» Это означает, что он имеет определенное внутреннее состояние, которое инкапсулировано из прямого внешнего доступа. У него есть время жизни: оно создается в какой-то момент выполнения программы и уничтожается в более поздний момент.
  2. «из которых не может быть создано более одного экземпляра». Это означает, что существует некоторый механизм явного , который принудительно предотвращает создание нескольких экземпляров. Конструктор и деструктор будут вызываться не более одного раза при выполнении программы.
  3. "который доступен по всему миру." Экземпляр, после его создания, доступен либо через глобальный вызов функции, либо через открытый статический член класса.

Глобальный объект не синглтон; это просто глобальный объект. Если возможно сделать больше одного, это не синглтон.

Теперь, когда определения устарели:

VBOManager (Singleton)

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

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

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

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

Я считаю, что синглтоны лучше всего подходят для концепций, которые принципиально уникальны. Существует одна файловая система, и поэтому имеет смысл иметь глобальную файловую систему. Сам OpenGL является глобальным (из-за его природы на основе C), хотя даже тогда можно переключать контексты рендеринга. Если когда-нибудь имеет смысл иметь две штуки, то, вероятно, не лучший вариант - синглтон.

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

GLUtils (синглтон)

Почему это объект ? Паттерн «Синглтон» относится к объекту, в котором может быть только один экземпляр в любой момент времени. Функции утилит являются просто глобальными функциями .

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

Графика (синглтон)

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

Однако учтите это. Так ли неправильно иметь более одного? Как часто вы будете проходить вокруг этого Graphics объекта? Сколько кода нужно для работы на уровне объекта Graphics? Я думаю, не так много. Какой-то базовый код инициализации и все.

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

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

4 голосов
/ 18 ноября 2011

Глобальные переменные <синглеты <шаблоны объектно-ориентированного проектирования </p>

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

3 голосов
/ 18 ноября 2011

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

Вряд ли можно использовать синглтоны.

VBOManager (Singleton) Этот «менеджер» в основном отвечает за распределение и, конечно, «управление» виртуальными машинами, которые используются для хранения текстурного наложения и координат вершин. Я управляю «чтением / записью» через этот объект, чтобы я мог кэшировать данные, записанные в vbo другими объектами, и возвращать указатели (избегая хранения дублирующихся данных, таких как 500 спрайтов с тем же отображением и координатами вершин).

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

TextureManager (Singleton, само за себя)

Так же, как VBO. Текстуры привязаны к контексту, но могут быть общими.

GLUtils (синглтон) Я в значительной степени использую это для объединения общих вызовов GL, которые различаются в зависимости от текущей платформы, например, GL или GLES. Пример, функции GLU (libglu не работает на Android, поэтому у меня есть пользовательская реализация)

Это определенно требует пространства имен. Однако не имеет ли больше смысла инкапсулировать функциональность в таких классах, как GLUTesselator, GLUQuadric и т. Д., И учитывать несколько экземпляров?

Графика (Singleton)

Вы можете иметь несколько окон и контекстов.

InputManager (Singleton, не требует пояснений)

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

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

Лично я не вижу причин для синглетонов.

3 голосов
/ 18 ноября 2011

Я также думаю, что вы злоупотребляете Singletons.Предложения для перечисленных вами примеров:

VBOManager

Управление VBO представляет интерес только для классов, содержащих 3D-сетки.Преобразуйте это в закрытые или защищенные переменные класса и члены класса Mesh, чтобы только сетки имели доступ к этой функции.

TextureManager

Та же идея, что и у VBO,Это должно быть видно только экземплярам текстуры, так что это должно быть в классе Texture как члены / переменные класса.

GLUtils

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

Графика

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

InputManager

Не уверен, что это вообще нужно.Что оно делает?Очевидно, что вам нужно иметь объекты Event, но обычно эти события отправляются в приложение с помощью какого-либо обратного вызова, поэтому приложению никогда не потребуется общаться с объектом InputManager.Вместо этого у вас может быть фоновый поток, который прослушивает события, а когда он получен, он помещает его в очередь событий, которую приложение читает, или отправляет заинтересованным слушателям через функцию обратного вызова.

3 голосов
/ 18 ноября 2011

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

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

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

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

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

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

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

1 голос
/ 18 ноября 2011

Это зависит. 0) Как говорили другие, синглтон (или, лучше сказать, глобальное состояние) имеет смысл, если он представляет нечто, существующее только один раз (например, файловую систему, но действительно ли она только одна?)

Но зачем это возражать? (А не глобальная функция / переменная)

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

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

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

1 голос
/ 18 ноября 2011

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

Можешь проголосовать - придерживайся их - ты делаешь хорошую вещь.

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