Author: Christian.Queinnec@lip6.fr

## Autres éléments de l'extension objet

On présente dans cette section la déclaration de types <<objet>> et surtout les déclarations locales dans les classes. Ces dernières peuvent construire des constructeurs qui possèdent un environnement local, considéré comme un ensemble de variables de classe.

### Interface

Les interfaces de classes sont en général inférées par le système d'inférence de types, mais elles peuvent aussi être définies par une déclaration de type. Seules les méthodes publiques apparaissent dans ce type.

### Syntaxe

class type nom =
object
:
val nomi : typei
:
method nomj : typej
:
end

On peut ainsi définir l'interface de la classe `point` :

    # class type interf_point  =
     object
       method get_x : int
       method get_y : int
       method moveto : (int * int ) -> unit
       method rmoveto : (int * int )  -> unit
       method to_string : unit -> string
       method distance : unit -> float
     end ;;

L'intérêt de cette déclaration est de pouvoir utiliser le type défini pour une contrainte de type.

    # let seg_length (p1:interf_point) (p2:interf_point) =
      let x = float_of_int (p2#get_x - p1#get_x)
      and y = float_of_int (p2#get_y - p1#get_y) in
       sqrt ((x*.x) +. (y*.y)) ;;
    val seg_length : interf_point -> interf_point -> float = <fun>

Les interfaces ne peuvent masquer que les champs de variable d'instance et les méthodes privées. Elles ne peuvent en aucun cas masquer des méthodes abstraites ou des méthodes publiques.
C'est une limitation de leur usage comme le montre l'exemple suivant :

    # let p = ( new point_m1 (2,3) : interf_point);;
    Characters 11-29:
    This expression has type
      point_m1 =
        < distance : unit -> float; get_x : int; get_y : int;
          moveto : int * int -> unit; rmoveto : int * int -> unit;
          to_string : unit -> string; undo : unit -> unit >
    but is here used with type
      interf_point =
        < distance : unit -> float; get_x : int; get_y : int;
          moveto : int * int -> unit; rmoveto : int * int -> unit;
          to_string : unit -> string >
    Only the first object type has a method undo

Néanmoins, les interfaces autorisent la relation d'héritage entre interfaces. Un intérêt de la définition d'interfaces est leur utilisation conjointe avec le mécanisme de modules. Ainsi il est possible de construire la signature d'un module, utilisant des types objets, en donnant uniquement la description des interfaces des classes.

### Déclarations locales dans les classes

Une déclaration de classe produit un type et un constructeur. Nous avons jusqu'à maintenant, pour la clarté de l'exposé, présenté ces constructeurs comme des fonctions sans environnement. En fait il est possible d'une part de définir des constructeurs qui n'ont pas besoin de valeurs initiales pour créer une instance. C'est-à-dire qui ne sont plus fonctionnels. D'autre part, il est possible d'effectuer des déclarations locales dans la classe. Ces valeurs peuvent être capturées dans le constructeur, permettant à ces valeurs d'être partagées par toutes les instances de la classe. Ces valeurs peuvent être considérées comme des variables de classe.

#### Constructeurs constants

Une déclaration de classe n'utilise pas forcément des valeurs initiales qui seront passées au constructeur. Par exemple la classe suivante :

    # class exemple1 =
       object
         method print () = ()
       end ;;
    class exemple1 : object method print : unit -> unit end
    # let p = new exemple1 ;;
    val p : exemple1 = <obj>

Le constructeur d'instances est constant. L'allocation n'a pas besoin de valeurs initiales pour les variables d'instance. En règle générale, il est préférable d'utiliser une valeur initiale comme `()` pour conserver le caractère fonctionnel du constructeur.

#### Déclarations locales pour les constructeurs

Une déclaration de classe peut s'écrire en utilisant directement l'abstraction.

    # class example2 =
       fun a ->
         object
           val mutable r = a
           method get_r = r
           method plus x = r <- r + x
        end;;
    class example2 :
      int ->
      object val mutable r : int method get_r : int method plus : int -> unit end

On s'aperçoit mieux du caractère fonctionnel du constructeur. Le constructeur est donc une fermeture. Celle-ci peut posséder un environnement qui lie des variables libres à un environnement de déclarations. La syntaxe des déclarations de classes autorise les déclarations locales à cette expression fonctionnelle.

#### Variables de classes

On appelle variables de classe, les déclarations connues au niveau de la classe, donc partagées par toutes les instances de cette classe. Le plus souvent ces variables de classe peuvent être utilisées en dehors de toute création d'instances. En Objective CAML, on peut faire partager des valeurs, en particulier modifiables, par toutes les instances d'une classe grâce au caractère fonctionnel du constructeur qui possède alors un environnement non vide.
Nous illustrons cette possibilité par l'exemple suivant qui permet de tenir à jour le nombre d'instances d'une classe. Pour cela on définit une classe abstraite paramétrée 'a om.

    # class virtual  ['a] om =
       object
         method  finalize ()  = ()
         method virtual destroy : unit -> unit
         method virtual to_string : unit -> string
         method virtual all : 'a list
       end;;

Puis on déclare la classe `'a lo` dont la fonction de construction contient les déclarations locales de `n` pour associer un numéro unique à chaque instance et de `l` qui contient la liste des couples (numéro, instance) de chaque instance encore active.

    # class ['a] lo =
       let l = ref []
       and n = ref 0 in
         fun s ->
           object(self:'b )
             inherit ['a] om
             val mutable num = 0
             val nom = s
             method to_string () = s
             method print () = print_string s
             method print_all () =
               List.iter (function (a,b) ->
                            Printf.printf "(%d,%s) " a (b#to_string())) !l
             method destroy () = self#finalize();
                                 l:= List.filter (function (a,b) -> a <> num) !l; ()
             method all = List.map snd !l
             initializer incr n; num <- !n;  l:= (num, (self :> 'a  om) ) :: !l ; ()
           end;;
    class ['a] lo :
      string ->
      object
        constraint 'a = 'a om
        val nom : string
        val mutable num : int
        method all : 'a list
        method destroy : unit -> unit
        method finalize : unit -> unit
        method print : unit -> unit
        method print_all : unit -> unit
        method to_string : unit -> string
      end

À chaque création d'une instance de la classe lo, l'initialisateur incrémente la référence `n` et ajoute le couple (numéro, `self`) à la liste `l`. Les méthodes print et `print_all` affichent respectivement l'instance réceptrice et toutes les instances de la classe contenues dans `l`.

    # let m1 = new lo "début";;
    val m1 : ('a om as 'a) lo = <obj>
    # let m2 = new lo "entre";;
    val m2 : ('a om as 'a) lo = <obj>
    # let m3 = new lo "fin";;
    val m3 : ('a om as 'a) lo = <obj>
    # m2#print_all();;
    (3,fin) (2,entre) (1,début) - : unit = ()
    # m2#all;;
    - : ('a om as 'a) list = [<obj>; <obj>; <obj>]

La méthode `destroy` enlève une instance de la liste des instances créées et appelle la méthode `finalize` pour effectuer une dernière action sur cette instance avant sa disparition de la liste. La méthode `all` retourne toutes les instances d'une classe créées en utilisant new.

    # m2#destroy();;
    - : unit = ()
    # m1#print_all();;
    (3,fin) (1,début) - : unit = ()
    # m3#all;;
    - : ('a om as 'a) list = [<obj>; <obj>]

Il est à noter que les instances des sous-classes sont aussi conservées dans cette liste. Rien n'empêche d'utiliser la même technique en particularisant certaines de ces sous-classes. Par contre les instances obtenues par copie (`Oo.copy` ou {< >}) ne sont pas concernées.