Précédent Index Suivant

Autres traits objet

Référencements : self et super

Il est pratique, dans la définition d'une méthode d'une classe, de pouvoir invoquer une autre méthode de cette même classe ou une méthode de la classe ancêtre. Pour cela Objective CAML autorise à nommer l'objet lui-même ou (les objets de) la classe ancêtre. Dans le premier cas, on indique le nom choisi aprés le mot clef object et dans le second cas, aprés la déclaration d'héritage.

Par exemple, pour définir la méthode to_string des points colorés, on peut invoquer la méthode to_string de la classe ancêtre et utiliser la nouvelle méthode get_color.

# class colored_point (x,y) c =
object(self)
inherit point (x,y) as super
val c = c
method get_color = c
method to_string () = super#to_string() ^ " de couleur " ^ self#get_color
end ;;
Il est possible de donner des noms quelconques pour la classe ancêtre et sa sous-classe, mais autant utiliser la terminologie objet (self ou this pour soi-même et super pour l'ancêtre). Choisir d'autre noms s'avère cependant utile dans le cas de l'héritage multiple pour différencier les ancêtres (voir page X).

Warning


On ne peut pas référencer une variable d'instance ancêtre dans le cas où l'on déclare une nouvelle variable d'instance du même nom qui masque la première. Seules les méthodes peuvent l'être.



Liaison retardée

On appelle liaison retardée (ou liaison dynamique) la détermination à l'exécution de la méthode à utiliser lors de l'envoi d'un message. La liaison retardée s'oppose à la liaison statique qui est déterminée à la compilation.

Quand un programme s'exécute, il doit établir la valeur associée à chaque identificateur rencontré. Cette liaison entre un identificateur et sa valeur peut s'effectuer soit à la compilation (liaison statique ou précoce), soit à l'exécution (liaison dynamique ou retardée). Ce problème se posait avant la programmation par objet. Par exemple la majorité des dialectes Lisp interprétés possèdent une liaison dynamique, et les dialectes ML compilés une liaison statique.

En Objective CAML la liaison des méthodes est retardée. C'est donc le receveur d'un message qui sait quel est le code à exécuter.

La déclaration précédente de la classe colored_point redéfinit la méthode to_string. Cette nouvelle définition utilise la méthode get_color de la classe. Définissons à présent une nouvelle classe colored_point_bis héritière de colored_point, qui redéfinit la méthode get_color (en testant la pertinence de la chaîne de caractères) mais qui ne redéfinit pas to_string.

# class colored_point_bis coord c =
object
inherit colored_point coord c
val true_colors = ["blanc"; "noir"; "rouge"; "vert"; "bleu"; "jaune"]
method get_color = if List.mem c true_colors then c else "INCONNUE"
end ;;
La méthode to_string est la même pour les objets appartenant aux deux classes de points colorés, cependant, elle n'aura pas le même comportement.

# let p1 = new colored_point (1,1) "bleu comme une orange" ;;
val p1 : colored_point = <obj>
# p1#to_string();;
- : string = "( 1, 1) de couleur bleu comme une orange"
# let p2 = new colored_point_bis (1,1) "bleu comme une orange" ;;
val p2 : colored_point_bis = <obj>
# p2#to_string();;
- : string = "( 1, 1) de couleur INCONNUE"
La liaison de get_color dans le corps de to_string n'est donc pas fixée lors de la compilation de la classe colored_point. Le code à exécuter lors de l'invocation de la méthode get_color est déterminée en fonction de liste des méthodes associée aux instances des classes colored_point et colored_point_bis. Ainsi, pour une instance de colored_point, l'envoi du message to_string provoque l'exécution de get_color définie dans la classe colored_point. Alors que le même envoi de message à une instance de colored_point_bis invoque bien la méthode de la classe ancêtre, mais cette dernière active la méthode get_color de la classe fille qui contrôle la pertinence de la chaîne représentant la couleur.

Représentation mémoire et envoi de messages

Un objet est découpé en deux parties : une variable et une fixe. La partie variable contient les variables d'instance, comme pour un enregistrement. La partie fixe correspond à une table des méthodes. Chaque objet d'une même classe, c'est-à-dire ayant été créé par la même fonction de construction, possède la même table qui est partagée par toutes les instances de cette classe.

Chaque méthode, de la classe de construction de l'objet, possède un numéro dans cette table. La table des méthodes est un tableau de fonctions. On suppose qu'il existe une instruction machine GETMETHOD(o,numéro) qui prend un objet et un numéro en tant que paramètre et retourne la fonction associée à ce numéro dans la table des méthodes de cette classe. On note f_n la fonction de numéro n de l'appel à GETMETHOD(o,n). La compilation d'un envoi de message, o#m, calcule le numéro n de la méthode m, et engendre le code suivant : GETMETHOD(o,n) o correspondant à l'application de la fonction f_n sur l'objet receveur o. La liaison retardée est implantée par l'appel à GETMETHOD lors de l'exécution.

L'envoi d'un message à self dans le corps d'une méthode est lui aussi compilé en une recherche du numéro du message, suivi de l'application de la fonction trouvée sur l'objet lui même.

Dans le cas d'un héritage, les méthodes redéfinies ou non conservent le même numéro. Seule la table change pour les redéfinitions. Ainsi l'envoi du message to_string sur une instance de la classe point appliquera la fonction de conversion d'un point alors que l'envoi du même message sur une instance de colored_point trouvera au même numéro la fonction correspondant à la méthode redéfinie pour tenir compte du champ couleur.

C'est cette identification des numéros qui garantit que le sous-typage (voir page 2) est cohérent du point de vue de l'exécution. En effet si un point coloré est explicitement forcé en point, l'envoi du message to_string calcule le numéro de méthode de la classe point qui coïncide avec celui de la classe colored_point. La recherche de la méthode s'effectuera dans la table associée à l'instance réceptrice, c'est-à-dire la table des colored_point.

L'implantation réelle d'Objective CAML est légèrement plus complexe, mais le principe de recherche dynamique de la méthode à employer reste le même.

Initialisation

Il est possible d'indiquer dans la définition de la classe des traitements à effectuer lors de la construction de l'objet. Cette initialisation peut faire tous les calculs et les accès aux champs ou aux méthodes de l'instance qu'il est normalement autorisé dans une méthode.

Syntaxe


initializer   expr



Il est possible d'indiquer dans la définition de la classe, une méthode initializer déclenchée immédiatement après la construction de l'objet. Cet "initialisateur" peut faire n'importe quel calcul et a accès aux champs et aux méthodes de l'instance (car celle-ci vient d'être créée).

Reprenons l'exemple de la classe point pour définir un point bavard qui annonce sa création.

# class verbose_point p =
object(self)
inherit point p
initializer
let xm = string_of_int x and ym = string_of_int y
in Printf.printf ">> Création d'un point en (%s %s)\n" xm ym ;
Printf.printf " a distance %f de l'origine\n" (self#distance()) ;
end ;;

# new verbose_point (1,1);;
>> Création d'un point en (1 1)
a distance 1.414214 de l'origine
- : verbose_point = <obj>
Une utilisation amusante et instructive des initialisateurs est de pouvoir suivre l'héritage des classes lors de la création d'instances. En voici un exemple autant pédagogique que dépouillé.

# class c1 =
object
initializer print_string "Création d'une instance de c1\n"
end ;;

# class c2 =
object
inherit c1
initializer print_string "Création d'une instance de c2\n"
end ;;

# new c1 ;;
Création d'une instance de c1
- : c1 = <obj>
# new c2 ;;
Création d'une instance de c1
Création d'une instance de c2
- : c2 = <obj>
La construction d'une instance de c2 passe par la construction d'une instance de la classe ancêtre.

Méthodes privées

Une méthode peut être déclarée privée par le mot clé private. Elle n'apparaîtra pas dans l'interface de la classe et donc dans le type de l'objet. Une méthode privée peut être invoquée dans la définition des autres méthodes de la classe mais ne peut pas l'être par une instance de cette classe. Par contre les méthodes privées sont héritées et pourront donc être utilisées dans les définitions de la hiérarchie.

Syntaxe


method private nom_methode = expr



Étendons la classe point en lui fournissant une méthode undo lui permettant de revenir sur son dernier mouvement. Pour réaliser cela, nous devons nous souvenir de la position occupée avant d'effectuer un mouvement. Nous introduisons donc deux nouveaux champs old_x et old_y avec leur méthode de mise à jour mem_pos. Nous ne voulons pas que l'utilisateur ait accès directement à cette méthode aussi la déclarons nous privée. Il faut redéfinir les méthodes moveto et rmoveto en mémorisant la position courante avant d'appeler les méthodes initiales de mouvement.

# class point_m1 (x0,y0) =
object(self)
inherit point (x0,y0) as super
val mutable old_x = x0
val mutable old_y = y0
method private mem_pos () = old_x <- x ; old_y <- y
method undo () = x <- old_x; y <- old_y
method moveto (x1, y1) = self#mem_pos () ; super#moveto (x1, y1)
method rmoveto (dx, dy) = self#mem_pos () ; super#rmoveto (dx, dy)
end ;;
class point_m1 :
int * int ->
object
val mutable old_x : int
val mutable old_y : int
val mutable x : int
val mutable y : int
method distance : unit -> float
method get_x : int
method get_y : int
method private mem_pos : unit -> unit
method moveto : int * int -> unit
method rmoveto : int * int -> unit
method to_string : unit -> string
method undo : unit -> unit
end
Remarquons que la méthode mem_pos apparaît dans le type point_m1 précédée du mot clé private, elle ne pourra donc pas être invoquée par une instance de point alors que la méthode undo le pourra. C'est le même cas de figure que pour les variables d'instance. Si les champs old_x et old_y figurent dans l'affichage résultant de la compilation ils ne peuvent pas pour autant être manipulés directement (voir X).

# let p = new point_m1 (0, 0) ;;
val p : point_m1 = <obj>
# p#mem_pos() ;;
Characters 0-1:
This expression has type point_m1
It has no method mem_pos
# p#moveto(1, 1) ; p#to_string() ;;
- : string = "( 1, 1)"
# p#undo() ; p#to_string() ;;
- : string = "( 0, 0)"


Warning


Une contrainte de type peut rendre publique une méthode déclarée avec l'attribut private.




Précédent Index Suivant