Author: Christian.Queinnec@lip6.fr

## 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 clé 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, il est préférable d'invoquer la méthode `to_string` de la classe ancêtre et d'étendre son comportement en utilisant 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() ^  " [" ^ 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'autres noms s'avère cependant utile dans le cas de l'héritage multiple pour différencier les ancêtres (voir page ??).

### 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.

### Liaison retardée

On appelle liaison retardée 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. 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 dans les deux classes de points colorés, cependant, deux objets de chacune de ces classes n'auront pas le même comportement.

    # let p1 = new colored_point (1,1) "bleue comme une orange" ;;
    val p1 : colored_point = <obj>
    # p1#to_string();;
    - : string = "( 1, 1) [bleue comme une orange] "
    # let p2 = new colored_point_bis (1,1) "bleue comme une orange" ;;
    val p2 : colored_point_bis = <obj>
    # p2#to_string();;
    - : string = "( 1, 1) [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é en fonction des méthodes associées 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 la méthode de la classe ancêtre, 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 : l'une variable et l'autre fixe. La partie variable contient les variables d'instance, comme pour un enregistrement. La partie fixe correspond à une table des méthodes qui est partagée par toutes les instances de cette classe.
La table des méthodes est un tableau de fonctions et chaque méthode possède un numéro qui est son indice dans ce tableau. On suppose qu'il existe une instruction machine `GETMETHOD(o,n)` qui prend en paramètre un objet `o` et un numéro `n`. Elle retourne la fonction associée à ce numéro dans la table des méthodes. On note `f_n` le résultat de l'appel à `GETMETHOD(o,n)`. La compilation de l'envoi de message o#m calcule le numéro `n` de la méthode `m` et engendre le code de l'application `GETMETHOD(o,n)` à l'objet `o`. Il correspond à l'application de la fonction `f_n` à 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 dans 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 préservation des numéros qui garantit que le sous-typage (voir page ??) est cohérent du point de vue de l'exécution. En effet si un point coloré est explicitement contraint 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 par le mot clé initializer dans la définition de la classe, des traitements à effectuer lors de la construction de l'objet. Cette initialisation peut effectuer tous les calculs et les accès aux champs ou aux méthodes de l'instance qui sont normalement autorisés dans une méthode.

### Syntaxe

initializer expr

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 apparaîtra dans l'interface de la classe mais pas dans le type des instances de cette classe. Une méthode privée peut être invoquée dans la définition des autres méthodes de la classe mais ne peut pas être envoyée à 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érarchie3.

### Syntaxe

method private nom = 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 ??).

    # 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.