Круговая классовая зависимость

12

Разве плохой дизайн - иметь 2 класса, которые нужны друг другу?

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

Вы бы назвали это плохим дизайном? Я просто спрашиваю, потому что я не совсем уверен, и в настоящее время я все еще могу реорганизовать эти вещи.

shad0w
источник
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, но они немного отличаются тем, что не ограничивают программиста в создании экземпляров по желанию.

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

Ali1S232
источник
6

Разве плохой дизайн - иметь 2 класса, которые нужны друг другу?

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

С C ++ дело в том, что циклические зависимости не будут компилироваться так легко , поэтому лучше избавиться от них, чем тратить время на исправление компиляции.

Смотрите этот вопрос на SO для нескольких мнений.

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

Нет, это все же лучше, чем поместить все в один класс.

Это не так здорово, но на самом деле это довольно близко к большинству реализаций, которые я видел. Обычно у вас есть класс менеджера для состояний игры ( будьте осторожны! ) И класс рендерера, и довольно часто они являются синглетонами. Таким образом, круговая зависимость «скрыта», но потенциально она есть.

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

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

Лоран Кувиду
источник
Почему это будет запах кода? Большинство объектов с родительскими и дочерними отношениями должны видеть друг друга.
Кикаймару
4
Потому что это самый простой способ не определить, какой класс за что отвечает. Это легко заканчивается двумя классами, которые настолько тесно связаны, что добавление нового кода может быть сделано в любом из них, поэтому они больше не разделены концептуально. Это также открытая дверь для кода спагетти: класс A вызывает класс B для этого, но B требует эту информацию от A и т. Д. Поэтому нет, я бы не сказал, что дочерний объект должен знать о своем родителе. Если возможно, лучше, если это не так.
Лоран Кувиду
Это ошибка скользкого склона. Статические классы тоже могут привести к плохим вещам, но классы util не являются запахом кода. И я действительно сомневаюсь, что есть какой-то «хороший» способ сделать так, чтобы у Scene были SceneNodes, а у SceneNode есть ссылка на Scene, поэтому вы не можете добавить его в две разные сцены ... И где бы вы остановились? Требуется ли A B, B требует C, а C требует A кодового запаха?
Кикаймару
Действительно, это как статические классы. Обращайтесь с ним осторожно, используйте его только в случае необходимости. Сейчас я говорю по опыту, и я не единственный, кто так думает , но на самом деле это вопрос мнения. Мое мнение таково, что в контексте, заданном ФП, это действительно вонючий.
Лоран Кувиду
Там нет упоминания об этом случае в этой статье в Википедии. И вы не можете сказать, что это «Неуместная близость», пока вы на самом деле не увидели эти классы.
Кикаймару
6

Да, часто считается плохим дизайном иметь два класса, которые напрямую ссылаются друг на друга. С практической точки зрения может быть сложнее следить за потоком управления через код, владение объектами и их время жизни могут быть сложными, это означает, что ни один класс не может быть повторно использован без другого, это может означать, что поток управления должен действительно жить вне обоих из этих классов в третьем классе «посредник», и так далее.

Тем не менее, два объекта очень часто ссылаются друг на друга, и разница здесь в том, что обычно отношения в одном направлении более абстрактны. Например, в системе Model-View-Controller объект View может содержать ссылку на объект Model и будет знать все о нем, имея возможность доступа ко всем его методам и свойствам, чтобы представление могло заполняться соответствующими данными. , Объект Model может содержать ссылку на View, чтобы он мог обновлять View, когда его собственные данные изменились. Но вместо того, чтобы Модель имела ссылку на View - что сделало бы Модель зависимой от View - обычно View реализует интерфейс Observable, часто всего с 1Update()функция, и модель содержит ссылку на наблюдаемый объект, который может быть представлением. Когда модель изменяется, она вызывает Update()все свои наблюдаемые объекты, и представление реализуется Update()путем обратного вызова модели и извлечения любой обновленной информации. Преимущество заключается в том, что Модель вообще ничего не знает о представлениях (и зачем это нужно?) И может быть повторно использована в других ситуациях, даже в тех случаях, когда представления отсутствуют.

У вас похожая ситуация в вашей игре. GameEngine обычно знает о GameStates. Но GameState не нужно знать все о GameEngine - ему просто нужен доступ к определенным методам рендеринга в GameEngine. Это должно вызвать небольшую тревогу в вашей голове, которая говорит о том, что либо (a) GameEngine пытается сделать слишком много вещей в одном классе, и / или (b) GameState не нужен весь игровой движок, только визуализируемая часть.

Три подхода к решению этой проблемы включают в себя:

  • Сделайте GameEngine производным от интерфейса Renderable и передайте эту ссылку в GameState. Если вы когда-либо реорганизуете часть рендеринга, вы просто должны убедиться, что она сохраняет тот же интерфейс.
  • Разложите часть рендеринга в новый объект Renderer и передайте его GameStates.
  • Оставь как есть. Возможно, в какой-то момент вам захочется получить доступ ко всем функциям GameEngine из GameState, в конце концов. Но имейте в виду, что программное обеспечение, которое легко поддерживать и легко расширять, обычно требует, чтобы каждый класс ссылался как можно меньше на внешнюю сторону, поэтому разбивает вещи на подобъекты и интерфейсы, которые выполняют один и хорошо определенная задача является предпочтительной.
Kylotan
источник
0

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

Википедия связи

сплоченность википедии

низкая связь, высокая когезия

stackoverflow на лучшую практику

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

avanderw
источник