J'ai étudié la programmation OO, principalement en C++, C # et Java. Je pensais que je l'avais bien comprise avec ma compréhension de l'encapsulation, de l'héritage et du polymorphisme.
Un concept fréquemment référencé lors de la discussion de OO est un "passage de message". Apparemment, c'est quelque chose qui n'est pas utilisé lorsque la programmation OO dans les langages traditionnels d'aujourd'hui, mais est pris en charge par Smalltalk.
Mes questions sont:
Qu'est-ce que le message passe? (Quelqu'un peut-il donner un exemple pratique?)
Le passage de message signifie simplement que (à un niveau très abstrait) le mécanisme fondamental de l'exécution du programme est que les objets s'envoient des messages. Le point important est que le nom et la structure de ces messages ne sont pas nécessairement fixés au préalable dans le code source et peuvent eux-mêmes être des informations supplémentaires. C'est une partie importante de ce qu'Alan Kay envisageait à l'origine comme une "programmation orientée objet".
Existe-t-il une prise en charge de ce "passage de message" en C++, C # ou Java?
Ces langages implémentent une version limitée du message passant par des appels de méthode. Limité car l'ensemble des messages pouvant être envoyés est limité aux méthodes déclarées dans une classe. L'avantage de cette approche est qu'elle peut être mise en œuvre de manière très efficace et qu'elle permet une analyse de code statique très détaillée (ce qui entraîne toutes sortes d'avantages utiles, comme l'achèvement du code).
Inversement, les langages qui implémentent la transmission de messages "réels" ont souvent aussi des définitions de méthode, comme un moyen pratique d'implémenter des gestionnaires de messages, mais permettent aux classes d'implémenter des gestionnaires de messages plus flexibles qui permettent à l'objet de recevoir des "appels de méthode" avec des noms arbitraires (non corrigés) au moment de la compilation).
Un exemple dans Groovy qui démontre la puissance de ce concept:
def xml = new MarkupBuilder(writer)
xml.records() {
car(name:'HSV Maloo', make:'Holden', year:2006) {
country('Australia')
record(type:'speed', 'Production Pickup Truck with speed of 271kph')
}
}
produira ce XML:
<records>
<car name='HSV Maloo' make='Holden' year='2006'>
<country>Australia</country>
<record type='speed'>Production Pickup Truck with speed of 271kph</record>
</car>
</records>
Notez que records
, car
, country
et record
sont des appels de méthode syntaxiquement, mais aucune méthode de ce nom n'est définie dans MarkupBuilder
. Au lieu de cela, il a un gestionnaire de messages catchall qui accepte tous les messages et interprète les noms de message comme le nom d'un élément XML, les paramètres comme attributs et les fermetures comme éléments enfants.
La transmission de messages est une manière différente de gérer le besoin dans le code OO pour un objet d'obtenir un autre objet (ou potentiellement lui-même) pour faire quelque chose.
Dans la plupart des langages modernes qui descendent de l'approche C++, nous le faisons avec des appels de méthode. Dans ce cas, l'objet appelé (via sa définition de classe) met une grande liste de ce que la méthode appelle qu'il accepte, puis le codeur de l'objet appelant écrit simplement l'appel:
public void doSomething ( String input )
...
other_object.dosomething ( local )
Pour les langages typés statiquement, le compilateur peut alors vérifier le type de la chose appelée et confirmer que la méthode a été déclarée. Pour les langages typés dynamiquement, cela est effectué au moment de l'exécution.
Mais ce qui se passe essentiellement, c'est qu'un ensemble de variables est envoyé à un bloc de code spécifique.
passage de message
Dans les langages de transmission de messages (comme l'Objectif C), au lieu des méthodes, il y a des récepteurs, mais globalement l'approche de les définir et de les appeler est à peu près la même - la différence est la façon dont elle est gérée.
Dans un message passé dans une langue, le compilateur peut vérifier que le récepteur que vous avez appelé existe, mais au pire, il affichera un avertissement pour dire que ce n'est pas le cas sûr que c'est là. En effet, au moment de l'exécution, ce qui se passera, c'est qu'un bloc de code sur l'objet récepteur sera appelé en passant à la fois le faisceau de variables et la signature du récepteur que vous souhaitez appeler. Ce bloc de code recherche ensuite le récepteur et l'appelle. Cependant, si le récepteur n'existe pas, le code renverra simplement une valeur par défaut.
En conséquence, l'une des bizarreries trouvées lors du passage de C++/Java -> Objective C est de comprendre que vous pouvez "appeler une méthode" sur un objet qui n'a pas été déclaré sur le type au moment de la compilation et qui n'existait même pas sur le type d'exécution ... et que l'appel n'entraînerait pas la levée d'une exception mais en fait un résultat renvoyé.
Les avantages de cette approche sont qu'elle aplatit la hiérarchie des sous-classes et évite la plupart des besoins en interfaces/types d'héritage/canards multiples. Il permet également aux objets de définir le comportement par défaut lorsqu'ils sont invités à faire quelque chose pour lequel ils n'ont pas de récepteur (généralement "si je ne le fais pas, transmettez la demande à cet autre objet"). Il peut également simplifier la liaison aux rappels (par exemple pour les éléments d'interface utilisateur et les événements temporisés) en particulier sur les langages typés statiquement tels que Java (vous pouvez donc demander au bouton d'appeler le récepteur "runTest" plutôt que d'appeler) la méthode "actionPerformed" sur la classe interne "RunTestButtonListener" qui fait l'appel pour vous).
Cependant, il semblerait que le développeur doive vérifier que l'appel qu'il pense passer est sur le bon objet avec le bon type et passer les bons paramètres dans le bon ordre, car le compilateur peut ne pas vous avertir et il fonctionnera parfaitement à l'exécution (en renvoyant simplement une réponse par défaut). Il y a aussi sans doute un impact sur les performances de la recherche supplémentaire et du passage de paramètres.
De nos jours, les langages typés dynamiquement peuvent donner beaucoup d'avantages aux messages transmis OO avec moins de problèmes.
Les architectures de passage de messages sont simplement des systèmes où chaque composant est indépendant des autres, avec un mécanisme commun pour passer des données entre eux. Vous pouvez considérer les appels de méthode comme une forme de transmission de messages, mais ce n'est pas pratique de le faire - cela confond le problème. En effet, si vous avez une classe avec des méthodes bien définies et du code qui appelle ces méthodes, le tout doit être compilé ensemble, couplant ainsi le code et l'objet. vous pouvez voir comment il est proche (lorsqu'un message est transmis et que le compilateur applique l'exactitude, mais il perd une grande partie de la flexibilité d'un système découplé).
Les architectures de passage de messages permettent souvent d'ajouter des objets au moment de l'exécution et, le plus souvent, de rediriger les messages vers un ou plusieurs objets. Je peux donc avoir un code qui diffuse un message `` Les données x sont mises à jour '' à tous les objets qui ont été chargés dans le système, et chacun d'entre eux peut prendre toutes les mesures qu'il souhaite avec ces informations.
Un exemple étrange est le Web. HTTP est un système de transmission de messages - vous passez un verbe de commande et un "paquet de données" à un processus serveur. (par exemple GET http:\myserver\url) Ni votre navigateur, ni le serveur Web ne se soucient des données que vous envoyez ou de l'endroit où vous les envoyez. Le serveur le transmettra au code qui emballera un autre "paquet" de données et vous le renverra. Aucun des composants de ce système ne sait quoi que ce soit sur le travail des autres ou ce qu'ils font, ils connaissent simplement le protocole utilisé pour la communication des messages.