Le système simulé est composé d'entités entre lesquelles existent des relations structurales (de composition, ensembliste), gérées par des services de la classe BasicEntity. Ces services, par exemple GetElement(int k), sont des méthodes (au sens du C++ natif) de la classe BasicEntity. Dans le code de la classe, ce sont de simples fonctions-membres :Entity* BasicEntity::GetElement(int k) { Entity* result = NULL; ... // recherche de l'élément de rang k return result; }Une telle méthode est spécifiée au niveau de la classe d'objets et est commune à tous les objets instances de la classe.
Les "méthodes" présentées dans cette page ne sont pas des services de la même nature : ce sont elles-mêmes des objets (complexes) qui permettent de spécifier des attributs fonctionnels d'autres objets. Un attribut essentiel de ces objets "méthodes" est leur corps qui, lui, est une simple fonction. Par exemple, si le service GetElement(int k) n'était pas prédéfini, on doterait la classe Entity d'une "méthode" GetElementMethod, héritant de EntityMethod, dont le corps serait le suivant :
Entity* getElementMethod_Body(EntityMethod* pM) { int rank = pM->GetIntArgValue(RANK); Entity* result = NULL; ... // recherche de l'élément de rang 'rank' return result; }On note ici, et on précisera plus loin, que :
- le corps de la méthode comporte un et un seul argument : un pointeur (ici pM, sur la méthode dont il est le corps ;
- une méthode est dotée d'arguments, eux-même objets de la classe Argument, et que leurs valeurs sont accessibles à l'intérieur du corps.
Les différentes spécialisations de méthodes
- Les entités (classe Entity) sont dotées de méthodes (classe EntityMethod) qui pécifient un comportement de l'entité ou calculent la valeur d'une fonction prenant l'entité en paramètre (fonction sur son état ou sur ses relations avec d'autres entités).
- Les processus (Process) sont dotés de méthodes (classe ProcessMethod) qui spécifient l'état du système lors de leur initialisation, l'effet qu'ils ont sur le système lors de leur poursuite, et une procédure accompagnant éventuellement leur arrêt.
- Les méthodes (classe EventMethod) des événements (Event) spécifient leur post-conséquences et la manière dont ils sont autogénérés.
- Les démons (DescValueMonitor et StructureMonitor) ont des méthodes (classe MonitorMethod) qui sont déclenchées réactivement.
- Enfin, les spécifications d'ensembles d'entités (classe EntitySpec) sont dotées de méthodes (classe EntitySpecMethod) qui spécifient les critères de sélection des entités ou la manière d'en construire la liste désirée.
Les attributs des méthodes
Une méthode en général (instance de Method) est caractérisée essentiellement par les propriétés suivantes :
- un corps, qui est un morceau de programme qui effectue le traitement caractéristique de la méthode et qui renvoie éventuellement une valeur au module à l'intérieur duquel la méthode a été invoquée ;
- un type, qui est celui de la valeur renvoyée par la méthode (plus précisément par le corps de la méthode), ou bien void si le corps de la méthode ne renvoie pas de valeur ;
- la liste des classes des arguments : les arguments de la méthode sont des valeurs, évaluées dynamiquement au moment de l'invocation de la méthode, qui sont exploitées par le corps de la méthode pour adapter le traitement à ces valeurs courantes ;
- la liste des arguments eux-mêmes, qui sont des instances indirectes de Argument (voir page 'Les classes de base pour représenter les arguments') ;
- enfin l'objet duquel la méthode est une connaissance fonctionnelle, par exemple une instance d'Entity pour les EntityMethod. Il est important de remarquer que l'adresse de cet objet, en tant que donnée membre de la classe, est accessible dans le corps de la méthode (pour suivre l'exemple, par DescribedEntity()), dont elle est alors de fait une sorte de paramètre constant.
A chaque type de valeur renvoyée par le corps correspond une sous classe de EntityMethod, ProcessMethod, EventMethod, , selon le cas. Dans DIESE, le type d'une méthode peut être l'un des suivants :
- un des types de descripteurs (voir page 'La classe de base pour tous les descripteurs'), à savoir un entier ou un réel (les types int, float et double de C), une chaîne de caractères (le type char* de C),
- une instance d'Entity ou encore un tableau de pointeurs sur des Entity (pEntityTab) ;
- les types void et bool de C.
Accessoirement, une méthode est caractérisée par les propriétés suivantes :
- son symbole de classe, utilement choisi de telle sorte qu'il rappelle la nature de l'information qu'elle renvoie ou le contenu du traitement qu'elle effectue. Par exemple, GET_ELEMENT_METHOD ;
- son nom de classe, qui a le même rôle que le symbole, mais sous la forme d'une chaîne de caractères. Par exemple, "getElementMethod".
L'invocation des méthodes
L'invocation d'une méthode, instance de Method, attachée par exemple à une entité, n'est pas très différente de l'invocation de la même méthode qui serait installée comme une fonction membre classique GetElement(int) de l'entité. L'appel classique :
int k = 1; Entity* pElem_k = pObj->GetElement(k);devient :
pObj->SetIntArgValue(GET_ELEMENT_METHOD, RANK, 1); Entity* pElem_k = pObj->ExecIntMethod(GET_ELEMENT_METHOD);ou encore :
EntityEntityMethod* pM = pObj->GetMethod(GET_ELEMENT_METHOD); pM->SetArgValue(RANK, 1); Entity* pElem_k = pM->Body(pM);où GET_ELEMENT_METHOD est le symbole de la sous-classe de Method dont le corps a le même code que GetElement(int), et dont pM est l'instance attachée à pObj.
Pourquoi les méthodes sont-elles des classes ?
L'utilisateur de DIESE peut trouver les avantages suivants au codage d'une connaissance fonctionnelle par une instance de Method :
- l'unification de la structure des entités du domaine : une série de fonctions membres est remplacée dans tous les cas par un unique attribut de type 'tableau de pointeurs sur des instances de Method' (la même remarque peut être faite sur l'unification de la représentation de variables d'état et des constantes, sous la forme de tableaux de pointeurs sur des instances de Descriptor) ;
- une connaissance fonctionnelle est séparée de l'entité (ou processus ou événement) sur laquelle elle porte, parce qu'implantée dans un objet Method, lequel n'est que référencé par l'entité ; cela augmente la déclarativité et la granularité de la représentation, et autorise l'attachement dynamique et temporaire de connaissances fonctionnelles à une entité ;
- conséquence de l'item précédent, la possibilité de distinguer deux instances de la même sous-classe d'Entity (ou de Process ou d'Event, etc.) par deux corps distincts de la même méthode ;
- enfin, les corps des instances de Method ont une signature commune unique (le seul argument est justement l'instance de Method dont le corps est attribut) : cette unicité permet de proposer un mécanisme de génération automatique de code (dans l'outil Solfege, associé à la bibliothèque DIESE).
Remarques diverses et générales
- Les constructeurs de base des sous-classes directes de Method établissent le type de l'objet décrit, précisé dans la documentation de chaque sous-classe.
- Les constructeurs par copie des sous-classes directes copient l'objet décrit (entité, processus ou événement, etc.) et invoquent le constructeur par copie de la classe de base Method.
- Les opérateurs d'affectation des sous-classes indirectes invoquent les opérateurs d'affectation des arguments, et la méthode à gauche pointe sur le même corps que celle à droite.
- Les services tels que DescribedEntity(pObj), DescribedProcess(pObj), ), désignent l'entité (ou le processus, ou l'événement, ) pObj comme l'objet dont la méthode est un attribut fonctionnel.
- Les services tels que DescribedEntity(), DescribedProcess(), ), renvoient l'entité (ou le processus, ou l'événement, ) dont la méthode est un attribut fonctionnel.
- Chaque sous-classe terminale de méthode possède un membre Body, le corps de la méthode, dont la valeur est un pointeur sur une fonction écrite par l'utilisateur. A l'image de l'exemple codé plus haut, l'invocation du corps peut se faire de deux manières :
- [un message à l'objet décrit : ] la séquence suivante demande à l'entité pObj d'exécuter sa méthode (à valeur entière) dont le symbole de classe est F, après lui avoir demander de valuer l'argument à valeur entière dont le symbole de classe est DATE :
pObj->SetIntArgValue(F, DATE, 12); int x = pObj->ExecIntMethod(F);- [un message à la méthode : ] la séquence suivante identifie d'abord la méthode, puis adresse des messages à cette méthode (valuation de son argument, puis invocation du corps) :
IntEntityMethod pM = pObj->GetMethod(F); pM->SetArgValue(DATE, 12); int x = pM->Body(pM);- Les services AssignBody affectent un corps à la méthode.