Чи можете ви "Порожні" конспект / класи?


10

Звичайно, можна, мені просто цікаво, чи раціонально проектувати таким чином.

Я робив клон прориву і робив якийсь дизайн класу. Я хотів використати спадщину, хоча мені і не потрібно, застосовувати те, що я навчився на C ++. Я думав про дизайн класу і придумав щось подібне:

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

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

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

Пізніше я планував отримати клас з NonMovableObject, наприклад Tileset: NonMovableObject. Мені було просто цікаво, чи часто використовуються "порожні" абстрактні класи або просто порожні класи ... Я помічаю, що так, як я це роблю, я просто створюю клас NonMovableObject просто заради категоризації.

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

Відповіді:


2

У C ++ у вас є множинне успадкування, тому буквально немає користі мати порожні класи. Якщо ви хочете пізніше успадкувати кулю від GameObject і MovableObject (скажімо, наприклад, ви хочете провести масив GameObjects і викликати метод Tick кожну четверту секунду, щоб змусити їх рухатися), це досить просто зробити.

Але в цій ситуації я б особисто запропонував вам запам'ятати аксіому « віддайте перевагу складу (інкапсуляції) над спадщиною », а також подивіться на державний зразок . Якщо ви хочете, щоб пізніше кожен об'єкт мав стан свого руху, передайте його в GameObject. Наприклад: DiagonalBouncingMovementState для м’яча, HoriazontalControledMovementState для весла, NoMovementState для цегли.

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

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

3) Найголовніше: коли ви хочете почати змінювати в грі те, як рухається м'яч та / або весло на основі цих жетонів, ви просто продовжуєте змінювати його стан руху (DiagonalMovementState стає DiagonalWithGravityMovementState).

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

0,02 дол


8

У Java "порожні" інтерфейси використовуються як маркери (наприклад, Serializable), оскільки під час виконання об'єкти можна перевіряти мокріше чи ні вони "реалізують" цей інтерфейс; але в С ++ мені це здається досить безглуздим.

ІМО, мовні особливості слід вважати інструментами, що допомагає вам досягти певних цілей, а не зобов'язань. Тільки тому, що ви можете використовувати функцію, не означає, що потрібно . Безглузде спадкування не робить програму кращою.


3

Ви не надмірно думаєте про це; ти думаєш, і це добре.

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

Не слід використовувати базові класи для категоризації. Це може означати тип перевірки. Це погана ідея.

Подумайте про те, щоб написати чисті абстракції, які визначають контракт ваших об'єктів. Контракт ваших об'єктів - це зовнішній інтерфейс. Його публічний інтерфейс. Що це робить.

Примітка. Важливо розуміти, що це не дані, які вони мають x, yа те, що вони роблять move().

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

Це не правильне використання спадщини. Завжди пам’ятайте, що успадкування - це найкрутіша форма зв'язку, яку ви, можливо, можете мати, і вам слід прагнути до нещільного з’єднання у будь-який час.

За своєю суттю NonMovableObjectне рухається. Її xі, yбезумовно, слід задекларувати const. За своєю суттю MoveableObjectпотрібно рухатися. Він не може оголосити його xі yяк const. Таким чином, це не ті самі дані, і їх не можна ділитися.

Розглянемо функціональність об'єктів або контракт. Що їм робити ? Отримайте це правильно, і дані надійдуть. Робота над одним об’єктом за раз. Турбуйтеся про кожного по черзі. Ви знайдете свої хороші конструкції для багаторазового використання.

Можливо, це зовсім не існує NonMovableObject. Що з чистої абстракції, GameObjectBaseяка визначає move()метод? Ваша система інстанціює GameObject, хто реалізує, move()і система переміщує лише ті, які потрібно перемістити.

Це лише початок; смак. Кроляча нора заходить набагато глибше. Ложки немає.


Хоча це правда, що ні вони, MovableObjectні NonMovableObjectможуть наслідувати один одного, вони обидва мають місце розташування; як такі, вони обидва повинні споживатися кодом, який, наприклад, хоче направити ціль монстра до об'єкта, не зважаючи на те, може цей об’єкт рухатися чи ні.
supercat

2

У ньому є додатки в згаданому ammoQ на C #, але в C ++ єдиним додатком було б, якщо ви хочете мати список покажчиків NonMovableObject.

Але тоді я думаю, що ви будете руйнувати об'єкти через покажчик NonMoveableObject, і в цьому випадку вам знадобляться віртуальні деструктори, і це вже не буде порожнім класом. :)


Я думав і про цей випадок, але що ви могли зробити з таким переліком об'єктів, що не мають жодної поведінки? Швидше за все, ви якимось чином дізнаєтесь справжній тип та застосували команду для доступу до доступних методів та полів - безумовно, запах коду.
користувач281377

Так, це хороший момент.
tenpn

1

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

Наприклад, ви хочете, щоб колекція вміщувала Кішок та Собак. У своєму дизайні ви вирішили, що вони мають багато спільного, тому маєте базовий клас Mamal і використовуєте цей тип у своїй колекції (наприклад, Список). Все добре. Тоді ви вирішите, що хочете додати до своєї колекції кілька Жаб, але вони не є ссавцями, тож одним із способів цього було б зробити підклас Жаб та ссавців тварини, але, можливо, ви не можете придумати, як багато Жаб та ссавців мають поширений, тому тварина залишається порожнім. Потім ви набираєте свою колекцію з Animal замість Mamal, і все добре.

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


Однак слід задати одне питання: якщо типи не мають нічого спільного, чому вони зберігаються в одній колекції? Немає жодної операції, яку ви можете виконати з усіма предметами колекції, яка ніби перемагає призначення такої колекції. Тепер можуть бути деякі штучні операції, які ви можете додати до обох, щоб спростити логіку - наприклад, реалізувати шаблон дизайну Visitor - в цей момент він знову може стати в нагоді, але тепер у них є щось спільне ...
Жюль,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.