Relations entre classes
Les classes d'une application peuvent entretenir entre elles deux
types de relations.
-
une relation d'agrégation, appellée Has-a .
Une
classe C2 est dans la relation Has-a avec une classe C1 si
C2 possède un champs du type de C1. Cette relation se
généralise en C2 possède au moins un champ du type de
C1.
- relation d'héritage : Is-a.
La classe C2 est sous-classe de la classe C1 lorsque C2 est
obtenue par extension du comportement de C1. C'est l'avantage
majeur de la programmation 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 de type 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 2.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 picture en lui ajoutant des points.
# 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égatation
La relation entre la classe picture et la classe
point est représentée graphiquement par la figure
2.2. Une flèche, dont l'origine comporte un losange et l'extrémité forme une pointe
vide, indique la relation d'agrégation.
Figure 2.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 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 dont 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 l'héritage indique le lien d'héritage d'une
autre_classe en lui passant (si il y a lieu)
les valeurs initiales pour la construction d'un objet d'autre_classe.
Syntaxe
inherit autre_classe e1 ... em [ as nom_ancetre ]
Il est possible d'associer un nom à la classe ancêtre pour accéder aux méthodes de celle-ci en cas
de redéfinition d'une méthode dans la classe descendante (voir page X).
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)
^
")"
^
" de couleur "
^
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) de couleur 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 et méthodes et les
méthodes redfinies. La figure 2.3 illustre la
relation entre la classe colored_point et son ancêtre
point.
Figure 2.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