У меня есть базовая 2D игра Tower Defense на C ++.
Каждая карта - это отдельный класс, который наследуется от GameState. Карта делегирует логику и код рисования каждому объекту в игре и устанавливает такие данные, как путь к карте. В псевдокоде логический раздел может выглядеть примерно так:
update():
for each creep in creeps:
creep.update()
for each tower in towers:
tower.update()
for each missile in missiles:
missile.update()
Объекты (крипы, башни и ракеты) хранятся в векторе указателей. Башни должны иметь доступ к вектору крипов и вектору ракет для создания новых ракет и определения целей.
Вопрос в том, где я объявляю векторы? Должны ли они быть членами класса Map и передаваться в качестве аргументов функции tower.update ()? Или объявлено глобально? Или есть другие решения, которые я пропускаю полностью?
Когда нескольким классам требуется доступ к одним и тем же данным, где эти данные должны быть объявлены?
источник
Ответы:
Когда вам нужен один экземпляр класса в вашей программе, мы называем этот класс сервисом . Существует несколько стандартных методов реализации сервисов в программах:
Одиночки . Около 10-15 лет назад, одиночки были большой дизайн-шаблон , чтобы знать. Однако в настоящее время на них смотрят свысока. Они намного проще для многопоточности, но вы должны ограничить их использование одним потоком за раз, что не всегда то, что вы хотите. Отслеживание времени жизни так же сложно, как и с глобальными переменными. Типичный синглтон-класс будет выглядеть примерно так:
Инъекция зависимости (DI) . Это просто означает передачу сервиса в качестве параметра конструктора. Служба уже должна существовать, чтобы передать ее в класс, поэтому две службы не могут полагаться друг на друга; в 98% случаев это то, что вы хотите (а для других 2% вы всегда можете создать
setWhatever()
метод и передать его позже) . Из-за этого у DI нет тех же проблем со связью, как у других опций. Его можно использовать с многопоточностью, поскольку каждый поток может просто иметь свой собственный экземпляр каждой службы (и делиться только теми, которые ему абсолютно необходимы). Это также делает код модульно-тестируемым, если вы заботитесь об этом.Проблема с внедрением зависимости заключается в том, что она занимает больше памяти; теперь каждому экземпляру класса нужны ссылки на каждый сервис, который он будет использовать. Кроме того, это раздражает, если у вас слишком много служб; Существуют фреймворки, которые смягчают эту проблему в других языках, но из-за отсутствия отражения в C ++, фреймворки DI в C ++, как правило, требуют больше усилий, чем просто делают это вручную.
Посмотрите эту страницу (из документации по Ninject, C # DI framework) для другого примера.
Внедрение зависимостей является обычным решением этой проблемы, и именно этот ответ вы найдете наиболее высоко оцененным по таким вопросам на StackOverflow.com. DI - это тип инверсии контроля (IoC).
Сервисный локатор . По сути, просто класс, который содержит экземпляр каждого сервиса. Вы можете сделать это, используя отражение , или вы можете просто добавить новый экземпляр к нему каждый раз, когда вы хотите создать новый сервис. У вас все еще та же проблема, что и раньше - Как классы получают доступ к этому локатору? - которая может быть решена любым из вышеперечисленных способов, но теперь вам нужно сделать это только для вашего
ServiceLocator
класса, а не для десятков услуг. Этот метод также тестируется модулем, если вы заботитесь о таких вещах.Сервисные локаторы - это другая форма инверсии контроля (IoC). Обычно платформы, которые выполняют автоматическое внедрение зависимостей, также имеют указатель службы.
XNA (среда программирования игр C # от Microsoft) включает в себя локатор служб; чтобы узнать больше об этом, смотрите этот ответ .
Кстати, ИМХО вышки не должны знать о крипах. Если вы не планируете просто перебирать список крипов для каждой башни, вы, вероятно, захотите реализовать некоторое нетривиальное разбиение пространства ; и такая логика не относится к классу башен.
источник
Я лично использовал бы здесь полиморфизм. Зачем иметь
missile
вектор,tower
вектор иcreep
вектор ... когда все они вызывают одну и ту же функцию;update
? Почему бы не иметь вектор указателей на некоторый базовый классEntity
илиGameObject
?Я считаю, что хороший способ проектирования - подумать «имеет ли это смысл с точки зрения владения»? Очевидно, что у башни есть способ обновить себя, но владеет ли карта всеми объектами на ней? Если вы идете по всему миру, вы говорите, что ничто не владеет башнями и крипами? Глобальный, как правило, плохое решение - он продвигает плохие шаблоны проектирования, но с ним гораздо проще работать. Подумайте над тем, чтобы взвесить «хочу ли я закончить это?» и «хочу ли я что-то, что я могу использовать повторно»?
Одним из способов решения этой проблемы является система обмена сообщениями. Он
tower
может отправить сообщениеmap
(к которому у него есть доступ, может быть, ссылка на его владельца?), Что он ударилcreep
, аmap
затем сообщает, чтоcreep
его ударили. Это очень чисто и разделяет данные.Другой способ - просто найти на карте то, что она хочет. Однако здесь могут быть проблемы с порядком обновления.
источник
Это тот случай, когда строгое объектно-ориентированное программирование (ООП) выходит из строя.
Согласно принципам ООП, вы должны группировать данные с соответствующим поведением, используя классы. Но у вас есть поведение (нацеливание), которому нужны данные, которые не связаны друг с другом (башни и крипы). В этой ситуации многие программисты будут пытаться связать поведение с частью данных, которые ему нужны (например, вышки обрабатывают нацеливание, но не знают о крипах), но есть другой вариант: не группировать поведение с данными.
Вместо того, чтобы делать поведение прицеливания методом класса башни, сделайте его свободной функцией, которая принимает в качестве аргументов башни и ползучести. Для этого может потребоваться сделать больше членов, оставшихся в башне, и классы ползучести, и это нормально. Сокрытие данных полезно, но это средство, а не самоцель, и вы не должны быть его рабом. Кроме того, закрытые члены - не единственный способ контролировать доступ к данным - если данные не передаются в функцию и не являются глобальными, они эффективно скрыты от этой функции. Если использование этого метода позволяет избежать глобальных данных, возможно, вы улучшаете инкапсуляцию.
Крайним примером этого подхода является архитектура системы сущностей .
источник