Précédent Index Suivant

Classes, objets et méthodes

L'extension objet d'Objective CAML s'intègre au noyau fonctionnel et au noyau impératif du langage, mais aussi à son système de types. C'est ce dernier point qui en fait son originalité. On obtient ainsi un langage objet, typé statiquement, avec inférence de types. Cette extension permet de définir des classes et des instances, autorise l'héritage entre classes (y compris l'héritage multiple), accepte les classes paramétrées et les classes abstraites. Les interfaces de classes sont engendrées par leur définition mais peuvent être précisées par une signature, à l'instar de ce qui est fait pour les modules.

Terminologie objet

Nous décrivons brièvement dans ce paragraphe les principaux éléments du glossaire de la programmation objet.
classe
une classe est un ensemble agrégé de champs de données (appelés des <<variables d'instance>>) et de traitements (appelés <<champs de méthode>> ou <<méthodes>>).
objet
un objet est un élément (ou instance) d'une classe. Un objet possède les comportements de la classe à laquelle il appartient. L'objet est le composant effectif des programmes (c'est lui qui calcule) alors que la classe est plutôt une définition ou une spécification pour l'ensemble des instances à venir.
méthode
une méthode est une action qu'est à même d'effectuer un objet.
envoi de message
un envoi de message à un objet est la demande faite à ce dernier d'exécuter une de ses méthodes. On pourra également dire que l'on invoque une méthode.

Déclaration d'une classe

La syntaxe la plus simple pour définir une classe est la suivante. Nous enrichirons cette définition tout au long du chapitre.

Syntaxe


class nom_classe p1 p2 ... pn =
  object
     .........
     variables d'instance, méthodes et autres
     .........
  end



p1, p2, ..., pn sont les paramètres que prendra le constructeur de cette classe. Une classe peut n'avoir aucun paramètres.

Une variable d'instance se déclare par :

Syntaxe


    val ident = expr
ou
    val mutable ident = expr

Dans la mesure où un champ de données est déclaré mutable, il est possible d'en modifier sa valeur. Dans le cas contraire, la valeur est celle calculée à la création de l'objet par l'évaluation de expr.

Une méthode se déclare par :

Syntaxe


    method ident = expr
ou
    method ident p1 p2 ... pn = expr

Si la méthode est une fonction, on peut utiliser la notation simplifiée en notant les paramètres à gauche du signe =.

Commençons par l'inévitable classe point; elle contient :

# 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 moveto (a,b) = x <- a ; y <- b
method rmoveto (dx,dy) = x <- x + dx ; y <- y + dy
method to_string () =
"( " ^ (string_of_int x) ^ ", " ^ (string_of_int y) ^")"
method distance () = sqrt (float(x*x + y*y))
end ;;
Remarquons que des méthodes peuvent avoir un comportement similaire à une fonction, c'est à dire renvoyer une valeur; sans toutefois avoir de paramètre, même pas (). C'est le cas de get_x et de get_y.

De cette déclaration, le système infère deux choses.

class point :
int * int ->
object
val mutable x : int
val mutable y : int
method distance : unit -> float
method get_x : int
method get_y : int
method moveto : int * int -> unit
method rmoveto : int * int -> unit
method to_string : unit -> string
end


D'une part, un type pour les objets de cette classe. Ce type sera abrégé par le nom point. Le type d'un objet est la liste des noms des méthodes de sa classe avec leur type. Dans notre exemple, point est une abréviation pour :
  < distance : unit -> unit; get_x : int; get_y : int;
    moveto : int * int -> unit; rmoveto : int * int -> unit;
    to_string : unit -> unit >
Il est possible de définir de tels types directement.

# type simple_point = < get_x : int; get_y : int; to_string : unit -> unit > ;;
type simple_point = < get_x : int; get_y : int; to_string : unit -> unit >


D'autre part, un constructeur d'instances de la classe point, de type int*int  -> point, qui permet de construire un objet point à partir des valeurs initiales fournies en argument. Ici, on construit un point à partir d'un couple d'entier (sa position initiale). La fonction de construction point sera utilisée avec le mot réservé new.

Remarque


Le type point ne reprend pas la totalité des informations affichées aprés la déclaration de la classe. Les variables d'instance n'apparaissent pas dans le type. On ne peut accéder à ces valeurs que par l'entremise d'une méthode.

On prend comme convention dans ce chapitre de définir les accesseurs aux champs de données comme des méthodes sans paramètre. Les autres méthodes auront toujours au moins un paramètre.

Warning


Une classe est considérée comme une déclaration de type. Il ne peut donc pas contenir une variable de type libre.

Nous reviendrons sur ce point quand nous aborderons les coercions de type (page X) et les classe paramétrées (page X).

Notation graphique des classes

On adapte la notation UML à la syntaxe des types d'Objective CAML. Les classes se notent par un rectangle constitué de trois parties : La figure 2.1 donne un exemple de représentation graphique de la classe chameau.


Figure 2.1 : Représentation graphique d'une classe


On peut ajouter l'information de types pour les champs d'une classe.

Création d'une instance

Un objet est une valeur d'une classe, appelée <<instance>> de cette classe. Cette instance est créée par la primitive de construction générique new à qui l'on indique la classe et les valeurs d'initialisation.

Syntaxe


new nom_classe v1 ... vn
L'exemple suivant construit plusieurs instances de la classe point, à partir de valeurs initiales différentes.

# let p1 = new point (0,0);;
val p1 : point = <obj>
# let p2 = new point (3,4);;
val p2 : point = <obj>
# let coord = (3,0);;
val coord : int * int = 3, 0
# let p3 = new point coord;;
val p3 : point = <obj>
Le type des objets p1, p2 et p3 est point, abréviation de la liste des noms de méthodes avec leur type de la classe point.

En Objective CAML, le constructeur d'une classe est unique mais rien ne nous empêche de déclarer une fonction spécifique instancie_point de création de points :

# let instancie_point x = new point (x,x) ;;
val instancie_point : int -> point = <fun>
# instancie_point 1 ;;
- : point = <obj>


Envoi d'un message

L'envoi d'un message à un objet s'effectue par la notation # 2.

Syntaxe


o # m p1 ... pn
Le message m est envoyé à l'objet o. Les arguments p1 ... pn sont les arguments attendus par la méthode m. La méthode doit être connue dans la classe de l'objet, c'est-à-dire visible dans son type. Le type des paramètres d'appel doivent satisfaire les types des paramètres formels. L'exemple suivant montre différentes requêtes effectuées sur des objets de la classe point.

# p1#get_x;;
- : int = 0
# p2#get_y;;
- : int = 4
# p1#to_string();;
- : string = "( 0, 0)"
# p2#to_string();;
- : string = "( 3, 4)"
# if (p1#distance()) = (p2#distance())
then print_string ("c'est le hasard\n")
else print_string ("on pouvait parier\n");;
on pouvait parier
- : unit = ()


Du point de vue des types, les objets de type point peuvent être manipulés par les fonctions polymorphes d'Objective CAML comme n'importe quelle valeur du langage :

# p1 = p1 ;;
- : bool = true
# p1 = p2;;
- : bool = false
# let l = p1::[];;
val l : point list = [<obj>]
# List.hd l;;
- : point = <obj>


Warning


L'égalité entre deux objets est vérifiée uniquement dans le cas où ils sont physiquement identiques.



Ce point sera détaillé à l'étude de la relation de sous-typage (page X).


Précédent Index Suivant