Всякий раз, когда я могу, я стараюсь ограничить связь между объектами в модели запроса и-ответа. Существует подразумеваемое частичное упорядочение объектов в моей программе, так что между любыми двумя объектами 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.
Последний свободный момент, который нужно связать, - то, что я позволю вызывать любую чистую функцию, независимо от того частичного упорядочения, о котором я говорил. Чистые функции немного отличаются от методов тем, что они не могут изменяться или зависеть от изменяемого состояния или побочных эффектов, поэтому их не смущают вопросы.