Ogni volta che posso, provo a limitare la comunicazione tra oggetti a un modello di richiesta e risposta. Esiste un ordinamento parziale implicito sugli oggetti nel mio programma in modo tale che tra due oggetti A e B, ci possa essere un modo per A di chiamare direttamente o indirettamente un metodo di B o per B di chiamare direttamente o indirettamente un metodo di A , ma non è mai possibile che A e B si chiamino reciprocamente i metodi. A volte, ovviamente, si desidera avere una comunicazione a ritroso con il chiamante di un metodo. Ci sono un paio di modi in cui mi piace farlo, e nessuno dei due è callback.
Un modo è quello di includere più informazioni nel valore di ritorno della chiamata del metodo, il che significa che il codice client può decidere cosa fare con esso dopo che la procedura ha restituito il controllo.
L'altro modo è chiamare un oggetto figlio comune. Cioè, se A chiama un metodo su B e B deve comunicare alcune informazioni ad A, B chiama un metodo su C, dove A e B possono entrambi chiamare C, ma C non può chiamare A o B. L'oggetto A sarebbe quindi responsabile di ottenere le informazioni da C dopo che B restituisce il controllo ad A. Nota che questo non è fondamentalmente diverso dal primo modo che ho proposto. L'oggetto A può ancora recuperare le informazioni solo da un valore di ritorno; nessuno dei metodi dell'oggetto A viene invocato da B o C. Una variante di questo trucco è passare C come parametro al metodo, ma le restrizioni sulla relazione di C con A e B si applicano ancora.
Ora, la domanda importante è perché insisto a fare le cose in questo modo. Ci sono tre ragioni principali:
- Mantiene i miei oggetti più liberamente accoppiati. I miei oggetti possono incapsulare altri oggetti, ma non dipenderanno mai dal contesto del chiamante e il contesto non dipenderà mai dagli oggetti incapsulati.
- Mantiene facile ragionare sul mio flusso di controllo. È bello poter supporre che l'unico codice in grado di cambiare lo stato interno di
self
durante l'esecuzione di un metodo sia quel metodo e nessun altro. Questo è lo stesso tipo di ragionamento che potrebbe indurre a mettere mutex su oggetti simultanei.
- Protegge gli invarianti sui dati incapsulati dei miei oggetti. I metodi pubblici possono dipendere dagli invarianti e tali invarianti possono essere violati se un metodo può essere chiamato esternamente mentre un altro è già in esecuzione.
Non sono contrario a tutti gli usi dei callback. In linea con la mia politica di non "chiamare mai il chiamante", se un oggetto A invoca un metodo su B e gli passa un callback, il callback potrebbe non cambiare lo stato interno di A e che include gli oggetti incapsulati da A e il oggetti nel contesto di A. In altre parole, il callback può invocare metodi solo su oggetti datigli da B. Il callback, in effetti, ha le stesse restrizioni di B.
Un'ultima parte libera da legare è che permetterò l'invocazione di qualsiasi funzione pura, indipendentemente da questo ordinamento parziale di cui ho parlato. Le funzioni pure sono un po 'diverse dai metodi in quanto non possono cambiare o dipendere dallo stato mutevole o dagli effetti collaterali, quindi non c'è da preoccuparsi che confondano le cose.