Il passaggio di messaggi è un modo diverso di gestire la necessità nel codice OO affinché un oggetto ottenga un altro oggetto (o potenzialmente se stesso) per fare qualcosa.
Nella maggior parte dei linguaggi moderni che discendono dall'approccio C ++ lo facciamo con le chiamate di metodo. In questo caso l'oggetto chiamato (tramite la sua definizione di classe) mette un grande elenco di quale metodo chiama accetta e quindi il codificatore dell'oggetto chiamante semplicemente scrive la chiamata:
public void doSomething ( String input )
...
other_object.dosomething ( local )
Per i linguaggi tipizzati staticamente, il compilatore può quindi controllare il tipo di cosa chiamata e confermare che il metodo è stato dichiarato. Per le lingue tipizzate dinamicamente, ciò viene eseguito in fase di esecuzione.
Ma in sostanza ciò che accade è che un fascio di variabili viene inviato a un blocco di codice specifico.
Messaggio che passa
Nei linguaggi che trasmettono messaggi (come l'obiettivo C) invece dei metodi ci sono ricevitori, ma in generale l'approccio di definirli e chiamarli è più o meno lo stesso: la differenza sta nel modo in cui viene gestito.
In una lingua di messaggio passata, il compilatore può verificare l'esistenza del destinatario che hai chiamato, ma nella peggiore delle ipotesi si aprirà un avviso per dire che non è sicuro che sia lì. Questo perché in fase di esecuzione ciò che accadrà è che un blocco di codice sull'oggetto ricevente verrà chiamato passando sia il pacchetto di variabili sia la firma del destinatario che si desidera chiamare. Quel blocco di codice cerca quindi il destinatario e lo chiama. Tuttavia, se il ricevitore non esiste, il codice restituirà semplicemente un valore predefinito.
Di conseguenza una delle stranezze rilevate quando ci si sposta da C ++ / Java -> Obiettivo C è capire che è possibile "chiamare un metodo" su un oggetto che non è stato dichiarato nel tipo di compilazione e che non esisteva nemmeno il tipo di runtime ... e che la chiamata non comporterebbe il lancio di un'eccezione ma in realtà un risultato che verrà restituito.
I vantaggi di questo approccio sono che appiattisce la gerarchia delle sottoclassi ed evita la maggior parte delle esigenze di interfacce / eredità multipla / tipi di anatre. Inoltre, consente agli oggetti di definire il comportamento predefinito quando viene chiesto loro di fare qualcosa per cui non hanno un ricevitore (comunemente "se non lo faccio, inoltra la richiesta a questo altro oggetto"). Può anche semplificare il collegamento ai callback (ad esempio per gli elementi dell'interfaccia utente e gli eventi a tempo) in particolare su linguaggi tipicamente statici come Java (quindi puoi avere il pulsante per chiamare il ricevitore "runTest" piuttosto che chiamare il metodo "actionPerformed" sulla classe interna "RunTestButtonListener" che fa la chiamata per te).
Tuttavia, sembrerebbe a costo della necessità di ulteriori controlli da parte dello sviluppatore che la chiamata che pensano di fare sia sull'oggetto giusto con il tipo giusto e che passi i parametri giusti nell'ordine giusto, perché il compilatore potrebbe non ti avverte e funzionerà perfettamente in fase di esecuzione (solo restituendo una risposta predefinita). Probabilmente c'è anche un colpo di performance dal look-up extra e dal passaggio dei parametri.
Al giorno d'oggi, i linguaggi tipizzati dinamicamente possono offrire molti vantaggi del messaggio passato OO con meno problemi.