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 :
-
deux champs de données x et y contenant les coordonnées du point,
- six champs de 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 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 :
-
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 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).