Relations entre classes
Les classes d'une application peuvent entretenir entre elles deux
types de relations.
-
Une relation d'agrégation, appelée Has-a :
une classe C2 est dans la relation Has-a avec une classe C1 si
C2 possède un champ du type de la classe C1. Cette relation se
généralise en C2 possède au moins un champ du type de
la classe C1.
- Une relation d'héritage, appelée Is-a :
une classe C2 est sous-classe d'une classe C1 lorsque C2 est
obtenue par extension du comportement de C1. C'est l'avantage
majeur de la programmation par objet que de pouvoir étendre le
comportement d'une classe existante tout en continuant à utiliser le
code écrit pour la classe originale. Quand on étend une classe, la
nouvelle classe hérite de tous les champs, de données et de
méthodes, de la classe qu'elle étend.
Relation d'agrégation
Une classe C1 est en relation d'agrégation avec une classe
C2, si au moins une de ses variables d'instance est du type de la
classe C2. On indique l'arité de la relation quand elle est connue.
Exemple de relation d'agrégation
On définit une figure comme un ensemble de points. On déclare
pour cela la classe picture (voir figure 15.2)
dont un des champs est un tableau de
points. La classe picture entretient donc la relation
généralisée Has-a avec la classe point.
# class
picture
n
=
object
val
mutable
ind
=
0
val
tab
=
Array.create
n
(new
point(0
,
0
))
method
add
p
=
try
tab.
(ind)<-
p
;
ind
<-
ind
+
1
with
Invalid_argument("Array.set"
)
->
failwith
("picture.add:ind ="
^
(string_of_int
ind))
method
remove
()
=
if
(ind
>
0
)
then
ind
<-
ind-
1
method
to_string
()
=
let
s
=
ref
"["
in
for
i=
0
to
ind-
1
do
s:=
!
s
^
" "
^
tab.
(i)#to_string()
done
;
(!
s)
^
"]"
end
;;
class picture :
int ->
object
val mutable ind : int
val tab : point array
method add : point -> unit
method remove : unit -> unit
method to_string : unit -> string
end
On construit une figure en créant une instance de la classe
picture et en ajoutant les points désirés.
# let
pic
=
new
picture
8
;;
val pic : picture = <obj>
# pic#add
p1;
pic#add
p2;
pic#add
p3;;
- : unit = ()
# pic#to_string
();;
- : string = "[ ( 0, 0) ( 3, 4) ( 3, 0)]"
Notation graphique de l'agrégation
La relation entre la classe picture et la classe
point est représentée graphiquement par la figure
15.2.
Une flèche, dont l'origine comporte un losange et l'extrémité forme une pointe
vide, indique la relation d'agrégation. Dans cet exemple la classe
picture possède entre 0 et un nombre quelconque de points.
Figure 15.2 : relation d'agrégation
On indique par ailleurs au dessus de la flèche l'arité de la relation.
Relation d'héritage
C'est la relation essentielle de la programmation par objet. Quand une
classe c2 hérite d'une classe c1 elle récupère tous
les champs de la classe ancêtre. Elle peut alors définir de nouveaux
champs et même redéfinir des méthodes héritées pour les spécialiser.
Comme la classe ancêtre n'a pas été modifiée, les applications s'en
servant n'ont pas à être adaptées aux changements apportés à la nouvelle
classe.
La syntaxe de la relation d'héritage est la suivante :
Syntaxe
inherit nom1 p1 ...pn
[ as nom2 ]
Les paramètres p1, ..., pn sont ceux
attendus par le constructeur de la classe nom1.
Il est possible d'associer un nom à la classe ancêtre pour accéder aux
méthodes de celle-ci. On utilise pour cela le mot clé as.
Cette possibilité est particulièrement utile lorsque la classe
fille redéfinit une méthode de la classe ancêtre (voir page
??).
Exemple d'héritage simple
L'exemple classique est d'étendre la classe point en ajoutant
un attribut de couleur aux points. On définit alors la classe
colored_point qui hérite de la classe point. La
couleur est représentée par le champ c de type
string. On ajoute une méthode get_color qui
retourne la valeur de ce champ. La conversion vers une chaîne de
caractères doit alors tenir compte de cet attribut et sera donc
redéfinie.
# class
colored_point
(x,
y)
c
=
object
inherit
point
(x,
y)
val
mutable
c
=
c
method
get_color
=
c
method
set_color
nc
=
c
<-
nc
method
to_string
()
=
"( "
^
(string_of_int
x)
^
", "
^
(string_of_int
y)
^
")"
^
" ["
^
c
^
"] "
end
;;
class colored_point :
int * int ->
string ->
object
val mutable c : string
val mutable x : int
val mutable y : int
method distance : unit -> float
method get_color : string
method get_x : int
method get_y : int
method moveto : int * int -> unit
method rmoveto : int * int -> unit
method set_color : string -> unit
method to_string : unit -> string
end
Les valeurs initiales pour la construction d'un colored_point sont le couple
de coordonnées nécessaire pour la construction d'un point et la couleur du point
coloré.
L'ensemble des méthodes héritées ou nouvellement définies ou
redéfinies correspond à l'ensemble des comportements des instances de la classe.
# let
pc
=
new
colored_point
(2
,
3
)
"blanc"
;;
val pc : colored_point = <obj>
# pc#get_color;;
- : string = "blanc"
# pc#get_x;;
- : int = 2
# pc#to_string();;
- : string = "( 2, 3) [blanc] "
# pc#distance;;
- : unit -> float = <fun>
On dit que la classe point est la classe ancêtre de
la classe colored_point et que celle-ci est la classe
fille de celle-là.
Warning
La redéfinition d'une méthode par une classe fille doit respecter le
type de la méthode définie dans la classe ancêtre.
Notation graphique de l'héritage
La relation d'héritage entre classes se note par une flèche, dont la pointe est formée
par un triangle fermé,
allant de la classe fille vers la classe ancêtre. Dans la
représentation graphique de l'héritage, on ne fait figurer dans
la classe fille que les nouveaux champs, les nouvelles méthodes et les
méthodes redéfinies. La figure 15.3 illustre la
relation entre la classe colored_point et son ancêtre
point.
Figure 15.3 : Relation d'héritage
Comme il contient des méthodes supplémentaires, le type
colored_point est différent du type point.
Le test d'égalité entre deux instances de ces classes provoque l'affichage
d'un long message d'erreur qui rappelle le type complet de chaque
classe pour bien montrer leur différence.
# p1
=
pc;;
Characters 6-8:
This expression has type
colored_point =
< distance : unit -> float; get_color : string; get_x : int; get_y :
int; moveto : int * int -> unit; rmoveto : int * int -> unit;
set_color : string -> unit; to_string : unit -> string >
but is here used with type
point =
< distance : unit -> float; get_x : int; get_y : int;
moveto : int * int -> unit; rmoveto : int * int -> unit;
to_string : unit -> string >
Only the first object type has a method get_color