Залежність кругового класу


12

Це погано мати 2 класи, які потрібні один одному?

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

Ви б назвали це поганим дизайном? Я просто прошу, бо я не зовсім впевнений, і в цей час я все ще в змозі переробити ці речі.


2
Я був би дуже здивований, якби відповідь була так.
doppelgreener

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

Мої класи GameState - це такі, як вступ, фактична гра, меню тощо. GameEngine зберігає ці стаки в стек, тож я можу призупинити стан і відкрити меню, або зіграти в крессцену, ... Класи GameState повинні знати GameEngine, тому що двигун створює вікно і має контекст візуалізації .
shad0w

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

Відповіді:


0

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

Тепер у вашому випадку набагато краще мати два окремих класи для GameEnigne та GameState. Оскільки в основному ці двоє роблять різні речі, і пізніше ви можете визначити безліч класів, які успадковують GameState (як GameState для кожної сцени вашої гри). І ніхто не може заперечувати їхню потребу мати доступ один до одного. В основному GameEngine працює з іграми, тому він повинен мати вказівник на них. І GameState використовують ресурси, визначені в GameEngine (наприклад, надані, менеджер з фізики тощо).

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

Поки ми знаємо, що нам потрібна кругова залежність у дизайні. Існує кілька способів безпечно створити це:

  1. Як ви запропонували, ми можемо помістити вказівник кожного з них на інший, це найпростіше рішення, але воно може легко вийти з-під контролю.
  2. Ви також можете використовувати однотонний / мультитонний дизайн, за допомогою цього методу слід визначити свій клас GameEngine як синглтон, а кожен із цих GameStates - як мультитон. Хоча багато розробників вважають AntiPattern як однотонним, так і багатотонним, я віддаю перевагу такому дизайну.
  3. Ви можете використовувати глобальні змінні. Ей - це в основному те саме, що і Singleton / multiton, але вони мають невелику різницю у тому, що вони не обмежують програміста не створювати екземпляри за бажанням.

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


6

Це погано мати 2 класи, які потрібні один одному?

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

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

Дивіться це питання на SO для отримання ще кількох думок.

Ви б назвали [мій дизайн] поганим дизайном?

Ні, це все-таки краще, ніж ставити все в один клас.

Це не так здорово, але насправді досить близько до більшості реалізацій, які я бачив. Зазвичай у вас є клас менеджера для ігрових станів ( будьте обережні! ) Та клас візуалізації, і цілком звичайно, що вони є однотонними. Отже, кругова залежність "прихована", але потенційно вона є.

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

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


Чому це був би кодовий запах? Більшість об’єктів, які мають стосунки батько-дитина, повинні бачити один одного.
Кікаймару

4
Тому що це найпростіший спосіб не визначити, який клас відповідає за що. Він легко складається в двох класах, настільки глибоко з'єднаних, що додавання нового коду можна зробити в будь-якому з них, тож вони вже не концептуально розділені. Це також відкрита двері для коду спагетті: клас А для цього викликає клас B, але B потрібна ця інформація від A тощо. Тому ні, я б не сказав, що дочірній об’єкт повинен знати про свого батька. Якщо можливо, краще, якщо цього немає.
Лоран Кувіду

Це слизький хит схилу. Статичні класи теж можуть призвести до поганих речей, але корисні класи не мають кодового запаху. І я дуже сумніваюся, що існує якийсь "хороший" спосіб зробити такі речі, як у Scene є SceneNodes, а SceneNode має посилання на Scene, тому ви не можете додати її до двох різних сцен ... І де б ви зупинилися? Чи потрібний B, B вимагає C і C вимагає запаху коду?
Кікаймару

Дійсно, це так само, як статичні класи. Поводьтеся обережно, використовуйте його лише за потреби. Зараз я кажу з досвіду, і я не єдиний, хто думає так , але, справді, це питання думки. Моя думка, що в контексті, наданому ОП, це справді смердюче.
Лоран Кувіду

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

6

Часто поганим дизайном вважається наявність 2 класів, які безпосередньо стосуються один одного, так. На практиці може бути складніше прослідкувати за контрольним потоком через код, право власності на об'єкти та їх термін експлуатації може бути складним, це означає, що жоден клас не може бути повторно використаний без іншого, це може означати, що потік управління повинен дійсно жити поза обома цих класів у третьому класі «посередника» тощо.

Однак для двох об'єктів дуже часто посилаються один на одного, і різниця тут полягає в тому, що зазвичай відносини в одному напрямку є більш абстрактними. Наприклад, в системі Model-View-Controller об'єкт View може містити посилання на об'єкт Model і буде знати про нього, маючи доступ до всіх його методів і властивостей, щоб представлення може заповнити себе відповідними даними . Об'єкт Model може містити посилання назад на Перегляд, щоб він міг зробити оновлення Перегляду, коли змінилися його власні дані. Але замість того, що модель має посилання на представлення даних - що зробить модель залежною від виду - зазвичай View реалізує інтерфейс, що спостерігається, часто лише 1Update()функція, і Модель містить посилання на об'єкт, що спостерігається, який може бути видом. Коли Модель змінюється, вона закликає Update()всі свої Спостережні дані, а Перегляд реалізує Update(), зателефонувавши до Моделі та отримавши будь-яку оновлену інформацію. Перевага тут полягає в тому, що Модель взагалі нічого не знає про Перегляди (і навіщо це робити?), І може бути використана повторно в інших ситуаціях, навіть у тих, що не мають Перегляду.

У вас в грі схожа ситуація. GameEngine зазвичай буде знати про GameStates. Але GameState не потрібно знати все про GameEngine - йому просто потрібен доступ до певних методів візуалізації на GameEngine. Це повинно створити невелику тривогу у вашій голові, яка говорить про те, що: (a) GameEngine намагається зробити занадто багато речей у межах одного класу, і / або (b) GameState не потребує всього ігрового двигуна, а лише частину, що передається.

Три підходи до вирішення цього питання включають:

  • Зробіть так, щоб GameEngine виходив із інтерфейсу Renderable, і передайте цю посилання в GameState. Якщо ви коли-небудь рефакторируете частину візуалізації, вам просто потрібно забезпечити, щоб вона зберігала той самий інтерфейс.
  • Розділіть частину візуалізації на новий об’єкт Renderer і передайте її GameStates.
  • Залиште так, як є. Можливо, в якийсь момент ви захочете отримати доступ до всіх функцій GameEngine від GameState, врешті-решт. Але майте на увазі, що програмне забезпечення, яке легко обслуговувати та легко розширювати, вимагає, щоб кожен клас посилався на якомога менше зовнішнього боку, тому розбиття речей на суб'єкти та інтерфейси, які виконують єдину та добре визначене завдання є кращим.

0

Як правило, вважається хорошою практикою мати високу згуртованість з низьким зв'язком. Ось декілька посилань щодо з’єднання та згуртованості.

Вікіпедія зчеплення

Вікіпедія згуртованість

низька муфта, висока згуртованість

stackoverflow на кращій практиці

Маючи два класи, що посилаються один на одного, має високу зв’язок. Структура google guice має на меті досягти високої згуртованості з низьким зв'язком за допомогою введення залежності. Я б запропонував вам прочитати трохи більше теми, а потім зробити власний дзвінок, враховуючи ваш контекст.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.