Можете ли вы иметь «пустые» аннотации / классы?

10

Конечно, вы можете, мне просто интересно, рационально ли так проектировать.

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

GameObject -> базовый класс (состоит из элементов данных, таких как смещения x и y, и вектора SDL_Surface *

MovableObject: GameObject -> абстрактный класс + производный класс GameObject (один метод void move () = 0;)

NonMovableObject: GameObject -> пустой класс ... нет методов или членов данных, кроме конструктора и деструктора (по крайней мере, сейчас?).

Позже я планировал извлечь класс из NonMovableObject, например, Tileset: NonMovableObject. Мне просто интересно, часто ли используются «пустые» абстрактные классы или просто пустые классы ... Я замечаю, что, как я это делаю, я просто создаю класс NonMovableObject просто для категоризации.

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

ShrimpCrackers
источник

Ответы:

2

В C ++ у вас есть множественное наследование, поэтому использование этих пустых классов буквально не выгодно. Если вы хотите, чтобы Ball наследовал от GameObject и MovableObject позже (скажем, например, вы хотите удерживать массив GameObjects и вызывать метод Tick каждую n-ю секунду, чтобы заставить их всех двигаться), это достаточно просто сделать.

Но в этой ситуации я бы лично предложил вам вспомнить аксиому « предпочесть композицию (инкапсуляцию), а не наследование » и взглянуть на модель состояния . Если вы хотите, чтобы каждый объект имел свое состояние движения позже, передайте его GameObject. Например: DiagonalBouncingMovementState для шара, HoriazontalControlledMovementState для весла, NoMovementState для кирпича.

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

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

3) Самое главное: если вы хотите начать изменять в игре то, как движется мяч и / или весло, основываясь на этих токенах, вы просто продолжаете изменять его состояние движения (DiagonalMovementState становится DiagonalWithGravityMovementState).

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

$ 0,02

прецизионный самописец
источник
8

В Java «пустые» интерфейсы используются в качестве маркеров (например, Serializable), потому что во время выполнения объекты можно проверять, являются ли они «реализующими» этот интерфейс; но в C ++ это кажется довольно бессмысленным для меня.

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

user281377
источник
3

Вы не слишком обдумываете это; Вы думаете, и это хорошо.

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

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

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

Примечание: Важно понять , что это не то , что данные , которые он имеет x, yно то , что он делает move().

В вашем дизайне у вас есть класс GameObject. Вы полагаетесь на его данные, а не на его функциональность. Он чувствует ЭФФЕКТИВНАЯ и правильно использовать наследование для обмена , что кажется, общие общие данные, в вашем случае xи y.

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

По самой своей природе NonMovableObjectне движется. Его xи yнаверняка следует объявить const. По самой своей природе MoveableObjectнужно двигаться. Он не может объявить его xи yкак const. Таким образом, это не одни и те же данные, и ими нельзя делиться.

Учитывайте функциональность ваших объектов или контракт. Что они должны делать ? Получите это право, и данные придут. Работайте над одним объектом одновременно. Беспокойство о каждом по очереди. Вы найдете ваши хорошие проекты многоразового использования.

Возможно, нет NonMovableObjectвообще. Как насчет чистой абстракции, GameObjectBaseкоторая определяет move()метод? Ваша система создает экземпляры, GameObjectкоторые внедряют, move()и система перемещает только те, которые должны двигаться.

Это только начало; вкус. Кроличья нора идет намного глубже. Здесь нет ложки.

hiwaylon
источник
Несмотря на то, что ни то, ни другое MovableObjectне NonMovableObjectможет наследовать друг от друга, у них обоих есть местоположение; как таковые, они оба должны потребляться кодом, который, например, хочет направить цель монстра на объект, не обращая внимания на то, может ли этот объект двигаться или нет.
суперкат
2

В нем есть приложения на C #, такие как ammoQ, но в C ++ единственным приложением было бы, если бы вы хотели иметь список указателей NonMovableObject.

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

tenpn
источник
Я тоже думал об этом случае, но что вы могли бы сделать с таким списком объектов, которые не проявляют никакого поведения? Скорее всего, вы бы как-то узнали реальный тип и использовали бы приведение для доступа к доступным методам и полям - определенно запах кода.
user281377
Да, это хороший момент.
10
1

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

Например, вы хотите, чтобы коллекция содержала Кошек и Собак. В своем дизайне вы решаете, что у них много общего, поэтому используйте базовый класс Mamal и используйте этот тип в своей коллекции (например, List). Все хорошо. Затем вы решаете, что хотите добавить лягушек в свою коллекцию, но они не являются мамалками, поэтому одним из способов сделать это будет сделать лягушек и мамалок подклассом животных, но, возможно, вы не можете думать о том, что есть у лягушек и мамалок в Обычный, так что Животное остается пустым. Затем вы печатаете свою коллекцию с помощью Animal вместо Mamal, и все хорошо.

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

Стив
источник
Однако нужно задать один вопрос: если типы не имеют ничего общего, почему они содержатся в одной коллекции? Вы не можете выполнять никаких операций со всеми элементами коллекции, что в некоторой степени противоречит цели создания такой коллекции. Теперь могут быть некоторые искусственные операции, которые вы можете захотеть добавить к обеим, чтобы упростить логику - например, реализовать шаблон проектирования Visitor - в этот момент он может снова стать полезным, но теперь у них есть что-то общее ...
Жюль