Конечно, это зависит от ситуации. Но когда объект или система более низкого уровня обмениваются данными с системой более высокого уровня, следует ли отдавать предпочтение обратным вызовам или событиям, а не указателю на объект более высокого уровня?
Например, у нас есть world
класс , который имеет переменную - член vector<monster> monsters
. Когда monster
класс обменивается данными с world
, я должен предпочесть использовать функцию обратного вызова , то или я должен иметь указатель на world
класс внутри monster
класса?
architecture
software-engineering
Hidayat
источник
источник
Ответы:
Есть три основных способа, которыми один класс может общаться с другим, не будучи тесно связанным с ним:
Три тесно связаны друг с другом. Система событий во многих отношениях - это просто список обратных вызовов. Обратный вызов - это более или менее интерфейс с одним методом.
В C ++, я редко использую обратные вызовы:
C ++ не имеет хорошей поддержки для обратных вызовов, которые сохраняют свой
this
указатель, поэтому трудно использовать обратные вызовы в объектно-ориентированном коде.Обратный вызов - это в основном нерасширяемый интерфейс с одним методом. Со временем я обнаружил, что мне почти всегда нужно более одного метода для определения этого интерфейса, и достаточно одного обратного вызова.
В этом случае я бы, вероятно, сделал интерфейс. В вашем вопросе, вы на самом деле не по буквам, что на
monster
самом деле нужно сообщитьworld
. Предполагая, я бы сделал что-то вроде:Идея заключается в том, что вы вводите
IWorld
(это дерьмовое имя) только тот минимум,Monster
к которому нужно получить доступ. Его взгляд на мир должен быть как можно более узким.источник
Не используйте функцию обратного вызова, чтобы скрыть, какую функцию вы вызываете. Если вы должны добавить функцию обратного вызова, и этой функции обратного вызова будет назначена одна и только одна функция, то вы на самом деле не нарушаете связь вообще. Вы просто маскируете это другим слоем абстракции. Вы ничего не получаете (кроме, может быть, времени компиляции), но вы теряете ясность.
Я бы точно не назвал это наилучшей практикой, но это обычная схема, когда сущности, содержащиеся в чем-то, имеют указатель на своего родителя.
При этом, возможно, стоит потратить время на использование шаблона интерфейса, чтобы дать вашим монстрам ограниченный набор функций, которые они могут вызвать в мире.
источник
Обычно я стараюсь избегать двунаправленных ссылок, но если они мне нужны, я абсолютно уверен, что есть один метод их создания и один для их разрыва, чтобы вы никогда не получали противоречий.
Часто вы можете полностью избежать двунаправленной ссылки, передавая данные по мере необходимости. Тривиальный рефакторинг - сделать так, чтобы вместо того, чтобы монстр сохранял связь с миром, вы передавали мир, ссылаясь на методы монстра, которым он нужен. Еще лучше - передать интерфейс только для тех кусков мира, в которых монстр остро нуждается, то есть монстр не полагается на конкретную реализацию в мире. Это соответствует принципу разделения интерфейсов и принципу инверсии зависимостей , но не начинает вводить избыточную абстракцию, которую вы иногда можете получить с событиями, сигналами + слотами и т. Д.
В некотором смысле, вы можете утверждать, что использование обратного вызова - это очень специализированный мини-интерфейс, и это нормально. Вы должны решить, можете ли вы более осмысленно достичь своих целей с помощью одного набора методов в объекте интерфейса или нескольких различных методов в различных обратных вызовах.
источник
Я стараюсь избегать того, чтобы содержащиеся объекты вызывали свой контейнер, потому что я нахожу, что это приводит к путанице, это становится слишком простым для обоснования, оно становится чрезмерным и создает зависимости, которыми невозможно управлять.
На мой взгляд, идеальное решение для высших классов уровня, чтобы быть достаточно умны, чтобы управлять низы уровня. Например, мир, зная, чтобы определить, произошло ли столкновение между монстром и рыцарем, не зная ни о другом, лучше меня, чем монстр, спрашивающий мир, столкнулся ли он с рыцарем.
Другой вариант в вашем случае, я бы, вероятно, выяснил, почему класс монстров должен знать о мировом классе, и вы, скорее всего, обнаружите, что в мировом классе есть нечто, что можно разбить на собственный класс, который он делает смысл для класса монстра знать.
источник
Очевидно, вы не продвинетесь далеко без событий, но прежде чем даже начать писать (и разрабатывать) систему событий, вы должны задать вам реальный вопрос: почему монстр общается с мировым классом? Должна ли она на самом деле?
Давайте возьмем «классическую» ситуацию, монстр атакует игрока.
Монстр атакует: мир может очень хорошо определить ситуацию, когда герой находится рядом с монстром, и сказать монстру атаковать. Таким образом, функция в монстре будет:
Но мир (который уже знает монстра) не должен быть познан монстром. На самом деле, монстр может игнорировать само существование класса мира, который, вероятно, лучше.
То же самое, когда монстр движется (я позволил подсистемам получить существо и обработать для него вычисления / намерения перемещения, монстр был просто пакетом данных, но многие люди сказали бы, что это не настоящий ООП).
Суть в том, что события (или обратный вызов), конечно, великолепны, но они не являются единственным ответом на каждую проблему, с которой вы столкнетесь.
источник
Всякий раз, когда я могу, я стараюсь ограничить связь между объектами в модели запроса и-ответа. Существует подразумеваемое частичное упорядочение объектов в моей программе, так что между любыми двумя объектами A и B может существовать способ для прямого или косвенного вызова метода B или для B прямого или косвенного вызова метода A , но это никогда не возможно для а и в взаимно вызывать методы друг друга. Иногда, конечно, вы хотите иметь обратную связь с вызывающей стороной метода. Есть несколько способов, которые мне нравятся, и ни один из них не является обратным вызовом.
Одним из способов является включение дополнительной информации в возвращаемое значение вызова метода, что означает, что клиентский код должен решить, что делать с ним после того, как процедура вернет ему управление.
Другой способ - вызвать общий дочерний объект. То есть, если A вызывает метод на B, а B необходимо передать некоторую информацию A, B вызывает метод на C, где A и B могут одновременно вызывать C, но C не может вызывать A или B. Тогда объект A будет ответственный за получение информации от C после того, как B возвращает управление A. Обратите внимание, что это на самом деле принципиально не отличается от первого способа, который я предложил. Объект A все еще может извлечь информацию только из возвращаемого значения; ни один из методов объекта A не вызывается B или C. Разновидностью этого трюка является передача C в качестве параметра методу, но ограничения по отношению C к A и B все еще применяются.
Теперь важный вопрос: почему я настаиваю на том, чтобы делать так? Есть три основные причины:
self
время выполнения метода, - это один метод, а не другой. Это тот же тип рассуждений, который может привести к созданию взаимных исключений на параллельных объектах.Я не против всех видов использования обратных вызовов. В соответствии с моей политикой никогда не «вызывать вызывающего», если объект A вызывает метод на B и передает ему обратный вызов, обратный вызов не может изменить внутреннее состояние A, и это включает объекты, инкапсулированные A, и объекты в контексте А. Другими словами, обратный вызов может вызывать методы только для объектов, данных ему посредством B. Обратный вызов, по сути, находится под теми же ограничениями, что и B.
Последний свободный момент, который нужно связать, - то, что я позволю вызывать любую чистую функцию, независимо от того частичного упорядочения, о котором я говорил. Чистые функции немного отличаются от методов тем, что они не могут изменяться или зависеть от изменяемого состояния или побочных эффектов, поэтому их не смущают вопросы.
источник
Лично? Я просто использую синглтон.
Да, хорошо, плохой дизайн, не объектно-ориентированный и т. Д. Знаете что? Я не забочусь . Я пишу игру, а не демонстрацию технологий. Никто не собирается оценивать меня по коду. Цель состоит в том, чтобы сделать игру веселой, и все, что мне мешает, приведет к игре, которая будет менее веселой.
Будете ли вы когда-нибудь запустить два мира одновременно? Может быть! Может быть, вы будете. Но если вы не можете думать об этой ситуации прямо сейчас, вы, вероятно, не будете.
Итак, мое решение: сделать мир синглтоном. Вызовите функции на нем. Быть сделано со всем беспорядком. Вы можете передать дополнительный параметр каждой функции - и не ошибитесь, вот к чему это приведет. Или вы можете просто написать код, который работает.
Чтобы сделать это таким образом, требуется немного дисциплины, чтобы убрать вещи, когда они становятся грязными (это «когда», а не «если»), но нет никакого способа предотвратить беспорядочный код - либо у вас есть проблема со спагетти, либо тысячи проблема абстракции. По крайней мере, таким образом вы не пишете большое количество ненужного кода.
И если вы решите, что вам больше не нужен синглтон, обычно довольно просто избавиться от него. Требуется некоторая работа, проходит миллиард параметров, но это те параметры, которые вам все равно придется обойти.
источник