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 par 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 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'un objet est à même d'effectuer.
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 p1 ...pn =
  object
      :
    variables d'instance
      :
    méthodes
      :
  end

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

Une variable d'instance se déclare par :

Syntaxe


val nom = expr
ou
val mutable nom = 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 nom p1 ...pn = expr


Il existe d'autres clauses que val et method dans la déclaration d'une classe. Nous les introduirons au fur et à mesure des besoins.

Une première classe
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 les méthodes peuvent ne pas avoir de paramètre. C'est le cas de get_x et de get_y. Nous prendrons comme convention de définir les accesseurs aux variables d'instance par des méthodes sans paramètre.

Après la déclaration de la classe point, le système affiche le texte suivant :

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


Ce texte contient deux informations. 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 >
D'autre part, un constructeur d'instances de la classe point, de type int*int -> oint, qui permet de construire un objet point à partir des valeurs initiales fournies en argument. Ici, on construit un point à partir d'un couple d'entiers (sa position initiale). La fonction de construction point sera utilisée avec le mot réservé new.

Il est possible de définir des types de classe :

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


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.


Warning


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


Nous reviendrons sur ce point quand nous aborderons les contraintes de type (page ??) et les classes paramétrées (page ??).

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 15.1 donne un exemple de représentation graphique de la classe chameau.



Figure 15.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 à laquelle on indique la classe et les valeurs d'initialisation.

Syntaxe


new nom espr1 ...exprn
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>


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


nom1#nom2 p1 ...pn
Le message (la méthode) nom2 est envoyé à l'objet nom1. Les arguments p1, ..., pn sont ceux attendus par la méthode nom2. Elle doit être connue dans la classe de l'objet, c'est-à-dire visible dans son type. Les types 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é lors de l'étude de la relation de sous-typage (page ??).


Précédent Index Suivant