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 :
-
deux champs de données x et y contenant les coordonnées du point,
-
six méthodes :
-
deux méthodes d'accès aux champs de données (get_x et get_y),
-
deux procédures de déplacement absolu (moveto) et relatif
(rmoveto),
-
une méthode de présentation des données sous forme de
string (to_string),
-
une méthode de calcul de la distance du point par rapport
à l'origine (distance).
# 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 :
-
une partie portant le nom de la classe,
- une autre où figurent les attributs (champs de données) d'une
instance de la classe,
-
une dernière où sont inscrites les méthodes d'une instance de la
classe.
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 ??).