OOP Design Player и AI Controlled Unit. dynamic_cast?

Допустим, у меня есть тип Actor, который может быть любым размещаемым объектом на моем Уровне игры. У меня также есть Unit, который является дочерним классом Актера. Единица может быть либо игроком, либо героем, контролируемым AI.

И Unit также получил 2 дочерних класса: Player (игрок) и Hero (AI controlled unit). В классе Unit будет отображаться информация о движении, вращение и другие общие настройки, которые понадобятся для игрока и героя. В дочерних классах AI будет отличаться и больше функций, чем игрок.

Сейчас я столкнулся со следующей проблемой:

Функция принимает только Actor в качестве параметра (например, OnOverlap(Actor a*)), но OnOverlap() должен делать только что-то, если это класс Unit (герой или игрок). Поэтому мне понадобится что-то вроде instanceof() из Java в C++.

Обходным решением будет либо использование dynamic_cast, но я не уверен, что это хорошая идея для производительности. Или используйте виртуальные, но это не будет работать, если у Героя больше функций, чем у Игрока.

Или я должен попытаться создать новый дизайн ООП?

4 ответа

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

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

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

Одна вещь, которую вы должны знать, это то, что виртуальные методы - это только один вид полиморфизма, называемый динамическим полиморфизмом. Существует еще один способ получить различное поведение в зависимости от типа, который менее затрудняет выполнение: статический полиморфизм, метапрограммирование шаблона AKA. Но это не помогает на стороне сложности.

Теперь то, что вы действительно должны делать в этом случае, относится к отдельным вещам отдельно. Вероятно, вы захотите использовать этот OnOverlap() для Units: допустим, вы выполняете проверку столкновений только с Units, а не с Actors. Затем сохраните отдельный список Units, и этот OnOverlap() не виртуальный в классе Unit. Такое мышление часто является ключевым.


Необходимость использования dynamic_cast часто (хотя и не всегда) является признаком того, что у вас есть дефект дизайна. В этом случае, я думаю, вы это делаете.

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

void X::OnOverlap (Actor * actor) {
 actor->OnOverlapped(this);
}

OnOverlapped сам по себе является виртуальным. Это позволяет вам "делать правильные вещи" в случае актера, но ничего не делать или какое-то другое поведение по умолчанию во всем остальном. Вы правы, что в этом случае это означает, что вы можете работать только с публичным API-интерфейсом Actor. Вы должны были бы переместить что - нибудь, требующее Player дополнительных методы в Player реализацию OnOverlapped. Если вы действительно думаете, что по какой-то причине вы не можете этого сделать, вам стоит подумать над альтернативным дизайном на более высоком уровне (и, возможно, вам придется отредактировать свой вопрос, чтобы предоставить более подробную информацию о вашей архитектуре).

(*) Мое первоначальное чтение вашего вопроса заставило меня подумать, что OnOverlap уже не является методом Actor; что он был частью чего-то несвязанного. Если это часть Actor, вас может заинтересовать шаблон, называемый двойной отправкой, который может быть использован для такого типа проблемы "столкновения", а также метод, использующий динамическую диспетчерскую (виртуальную)).


Эта функция предопределена моим движком, где параметр должен содержать родительскую функцию "Актер". Поэтому, если какой-либо Актер сталкивается с Mesh, функция вызывается. В этом случае для меня я не вижу другого решения, чем получить экземпляр объекта Actor, чтобы узнать, является ли его игроком только что-то еще

Простой виртуальной функцией в базовом классе Actor может быть noop. Переопределите его в классе Unit и специализируйте его еще больше в Hero и Player, если это необходимо. Некоммерческий актер вызовет пустой метод. Чище, чем RTTI и экземпляр идиомы.

Хорошо иметь базовую реализацию по умолчанию, которая, как ожидается, будет переопределена специализированными потомками. Это точка абстрактных базовых классов, за исключением того, что в этом случае вы предоставляете реализацию по умолчанию, которая ничего не делает. Идея заключается в том, что вы отправляете сообщение "DoCollision" (виртуальная функция в C++). Вам все равно, какие классы справляются с этим или каким образом. Просто, что объект имеет "интерфейс" компиляции времени DoCollision и что вы правильно его отправили.


Этот метод называется Run-Time Type Information (RTTI) и проверяет, является ли вывод dynamic_cast NULL допустимой реализацией.

Другим методом является #include и используйте typeid(*a).name() чтобы получить имя строки типа.

licensed under cc by-sa 3.0 with attribution.