Author: Christian.Queinnec@lip6.fr

## Relations entre classes

Les classes d'une application peuvent entretenir entre elles deux types de relations.

1. 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.
2. 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