Précédent Index Suivant

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 type, 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_type =
  object
     method nom_methode : type
  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 print : unit -> unit
method distance : unit -> float
end ;;
L'intérêt de cette déclaration est de pouvoir utiliser le type défini pour une coercion 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. Néanmoins, elles utilisent aussi la relation d'héritage (entre interfaces).

# 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; print : unit -> unit;
rmoveto : int * int -> unit >
Only the first object type has a method to_string
En fait l'intérêt principal de le définition d'interfaces est leur utilisation avec le mécanisme modules. Ainsi il sera possible de construire la signature d'un module, utilisant des types objets, uniquement en donnant la description des interfaces de ces 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. De plus, il est possible d'effectuer des déclarations locales dans la classe. Ces valeurs peuvent être capturées dans le constructeur, permettant de partager ces valeurs pour toutes les instances de la classe. Ces valeurs peuvent être considérée 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éral, il est préférable d'utiliser une valeur initiale comme () pour conserver le caractère fonctionnel du constructeur, sinon il est évalué immédiatement.

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 de déclarations des classes autorise les déclarations locales à cette expression fonctionnelle.

Variables de classes

On appelle variables de classe, des déclarations connues au niveau de la classe, et donc partagées par toutes les instances de cette classe. Le plus souvent ces variables de classe peuvent être utilisée en dehors de toute création d'instances. En Objective CAML, on peut 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éfini 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 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. La méthode destroy num enlève l'instance réceptrice de la liste l. Les méthodes print affiche l'instance. La méthode print_all affiche toutes les instances de la classe, contenues dans l.

# let m1 = new lo "debut";;
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,debut) - : unit = ()
# m2#all;;
- : ('a om as 'a) list = [<obj>; <obj>; <obj>]
# m2#destroy();;
- : unit = ()
# m1#print_all();;
(3,fin) (1,debut) - : unit = ()
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, permettant d'effectuer un traitement sur toutes les instances créées de cette classe.

# m3#all;;
- : ('a om as 'a) list = [<obj>; <obj>]
# List.iter (fun x -> x#to_string()) (m3#all);;
Characters 36-42:
This expression has type ('a om as 'a) list but is here used with type
< all : ('b om as 'b) list; destroy : unit -> unit;
finalize : unit -> unit; to_string : unit -> unit >
list
Type
'c om as 'c =
< all : ('d om as 'd) list; destroy : unit -> unit;
finalize : unit -> unit; to_string : unit -> string >
is not compatible with type
< all : ('e om as 'e) list; destroy : unit -> unit;
finalize : unit -> unit; to_string : unit -> unit >
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 particulirisant certaines sous-classes.






Précédent Index Suivant