Sempre que posso, tento restringir a comunicação entre os objetos a um modelo de solicitação e resposta. Existe uma ordem parcial implícita nos objetos no meu programa, de modo que, entre dois objetos A e B, possa haver uma maneira de A chamar direta ou indiretamente um método de B ou de B chamar direta ou indiretamente um método de A , mas nunca é possível que A e B chamem mutuamente os métodos uns dos outros. Às vezes, é claro, você deseja ter uma comunicação com o chamador de um método. Há algumas maneiras pelas quais eu gosto de fazer isso, e nenhuma delas é de retorno de chamada.
Uma maneira é incluir mais informações no valor de retorno da chamada do método, o que significa que o código do cliente decide o que fazer com ele após o procedimento retornar o controle para ele.
A outra maneira é chamar um objeto filho mútuo. Ou seja, se A chama um método em B, e B precisa comunicar algumas informações para A, B chama um método em C, onde A e B podem chamar C, mas C não pode chamar A ou B. O objeto A seria responsável por obter as informações de C após B retorna o controle para A. Observe que isso não é fundamentalmente diferente da primeira maneira que propus. O objeto A ainda pode recuperar as informações apenas de um valor de retorno; nenhum dos métodos do objeto A é invocado por B ou C. Uma variação desse truque é passar C como parâmetro ao método, mas as restrições no relacionamento de C com A e B ainda se aplicam.
Agora, a questão importante é por que insisto em fazer as coisas dessa maneira. Existem três razões principais:
- Isso mantém meus objetos mais fracamente acoplados. Meus objetos podem encapsular outros objetos, mas eles nunca dependerão do contexto do chamador e o contexto nunca dependerá dos objetos encapsulados.
- Isso mantém meu fluxo de controle fácil de raciocinar. É bom poder assumir que o único código que pode alterar o estado interno
self
enquanto um método em execução é esse e não o outro. Esse é o mesmo tipo de raciocínio que pode levar alguém a colocar mutexes em objetos simultâneos.
- Ele protege invariantes nos dados encapsulados dos meus objetos. É permitido que métodos públicos dependam de invariantes, e esses invariantes podem ser violados se um método puder ser chamado externamente enquanto outro já estiver em execução.
Não sou contra todos os usos de retornos de chamada. De acordo com minha política de nunca "ligar para o chamador", se um objeto A chama um método em B e passa um retorno de chamada para ele, o retorno de chamada pode não alterar o estado interno de A e isso inclui os objetos encapsulados por A e o objetos no contexto de A. Em outras palavras, o retorno de chamada só pode invocar métodos em objetos dados a ele por B. O retorno de chamada, na verdade, está sob as mesmas restrições que B.
Um último ponto a perder é que permitirei a invocação de qualquer função pura, independentemente dessa ordem parcial de que estou falando. As funções puras são um pouco diferentes dos métodos, pois não podem mudar ou depender de estados mutáveis ou efeitos colaterais; portanto, não há preocupação em confundir assuntos.