Références :
Objective Caml propose une extension objet qui s'intégre au noyau fonctionnel et au noyau impératif, compatible avec le système de typage. L'association avec le polymorphisme confère au langage une puissance expressive importante. Il est à noter une limitation importante : Objective Caml n'intégre pas la notion de surcharge de méthodes (plusieurs définitions d'une même méthode) ... le système d'inférence de type risquerait sinon de rencontrer des ambiguités.
Une classe définit à la fois un type et un moule pour les objets. Elle contient des données et des méthodes.
Exemple de la classe point
#class point x_init y_init =
object
val mutable x = x_init
val mutable y = y_init
method get_x = x
method get_y = y
method set_x z = x <- z
method set_y z = y <- z
method move dx dy = x <- x+dx; y <- y+dy
method to_string ( ) =
"(" ^ (string_of_int x) ^ "," ^ (string_of_int y) ^ ")"
end;;
Créer une instance
#let p = new point 2 3;;
val p:point =<obj>
#new point;;
- : int -> int -> point =<fun>
Appeler les méthodes
# p#get_x;;
- : int = 2
# p#move 1 1;;
- : int = ()
# p#to_string();;
- : string = "(3,4)"
#class point_colore x_init y_init (c_init:string) =
object (me)
inherit point x_init y_init as mother
val mutable c = c_init
method get_color = c
method set_color nc = c <- nc
method to_string()=mother#to_string() ^ me#get_color
end;;
On remarque que l'argument de couleur de l'objet, à savoir "c_init", a été déclaré avec un type contraint "string". Ceci peut être utile si l'utilisation de l'argument ne permet d'en déterminer le type : une telle indétermination n'est pas acceptée par l'interpréteur. Dans le cas présent, cette contrainte de type est superflue car la méthode "to_string" permet de déterminer le type de "c_init".
Dans l'exemple précédent, il était utile de se référencer soit-même (pour invoquer "get_color"), ainsi que la classe mère (pour invoquer "to_string"). La référence sur soi-même s'obtient en faisant suivre "object" par un identificateur entre paranthèses (ici "me"). La référence sur la classe mère se fait en fin de la déclaration "inherit" en la faisant suivre par "as" et par un identificateur donné (ici "mother").
Voici maintenant comment créer et manipuler une instance de la classe "point_colore" :
#let p = new point_colore 2 3 "bleu";;
val p : point_colore = <obj>
#p#get_color;;
-:string = "bleu"
Liaison retardée
Lorsque des méthodes sont redéfinies dans des classes filles, il est nécessaire de déterminer de quelle méthode il est question lors d'une invocation donnée. On désigne par "liaison retardée", le fait que la détermination de la méthode à utiliser lors d'une invocation est dynamique : elle se fait à l'exécution, en fonction du contexte.
Classe abstraite
Une classe peut être déclarée virtuelle ("class virtual nom = object ... end") lorsque certaines méthodes sont déclarées mais ne possèdent pas de corps. Ces méthodes sont également déclarées virtelles ("method virtual nom : type"). Une telle classe ne peut pas être instanciée (par "new ...") et permet de définir un cadre générique pour ses classes dérivées.
Exemple :
# class virtual printable () =
object(self)
method virtual to_string : unit -> string
method print () = print_string (self#to_string())
end ;;
# class rectangle (p1,p2) =
object
inherit printable ()
val mutable llc = (p1 : point)
val mutable ruc = (p2 : point)
method to_string () = "[" ^ p1#to_string() ^ "," ^ p2#to_string() ^ "]"
end ;;
Les classes paramétrées mettent en oeuvre le polymorphisme dans la définition des classes.
Exemple :
On désire construire une paire d'éléments. La définition suivante est incorrecte :
#class pair x0 y0 =
object
val x = x0
val y = y0
method fst = x
method snd = y
end;
La définition précédente est incorrecte car elle conduit à utiliser des paramètres de types (pour les types de x0 et de y0) que le système d'inférence de types ne peut calculer. Les paramètres de type nécessaires doivent alors être explicitement indiqués comme dans la construction suivante qui est correcte :
#class ['a, 'b] pair (x0:'a) (y0:'b) =
object
val x = x0
val y = y0
method fst = x
method snd = y
end;
On peut alors instancier et utiliser la classe de la manière suivante :
#let p = new pair 2 'x';;
val p : (int, char) pair = <obj>
#p#fst;;
- : int = 2
Autre exemple permettant de construire des piles génériques
#class ['a] pile =
object(this)
val mutable p : 'a list = []
method est_vide = (p = [])
method lire_sommet = List.hd p
method empiler x = p <- x::p
method depiler = if (this#est_vide)
then failwith("pile vide")
else p <- List.tl p;
method to_string () = p
end;;
Dérivation dans les classes paramétrées
Voici est exemple :
# class ['a,'b] acc_pair (x0 : 'a) (y0 : 'b) =
object
inherit ['a,'b] pair x0 y0
method get1 z = if x = z then y else raise Not_found
method get2 z = if y = z then x else raise Not_found
end;;
On peut aussi dériver une classe paramétrée pour spécifier ses paramètres, comme dans l'exemple suivant :
# class pair_point (p1,p2) =
object
inherit [point,point] pair p1 p2
end;;
On implémentera les spécifications suivantes :
point
sera augmentée par une méthode calculant la distance du point courant avec un autre point.
figure
est abstraite et contient :
to_string
qui affiche les caractéristiques de l'objet sous la forme d'une chaîne de caractères.
polygone
contient un attribut liste de points qui le caractérisent. Cette classe doit implémenter les méthodes abstraites de la classe figure
.
triangle
et rectangle
caractérisent respectivement des polygones à 3 et 4 sommets.
cercle
est caractérisée par un centre (l'origine de la figure) et un rayon. Cette classe doit implémenter les méthodes abstraites de la classe figure
.
Ecrire un petit programme testant toutes ces classes.
Graphics
.