Author: Christian.Queinnec@lip6.fr

## Étendre les composants

On appelle composant un regroupement de données et de traitements sur ces données. Dans le modèle fonctionnel/modulaire, un composant comprend la définition d'un type et des fonctions manipulant ce type. De même un composant dans le modèle objet correspond à une hiérarchie de classes, héritant d'une même classe et donc possédant tous les comportements de celle-ci. Le problème de l'extensibilité des composants consiste à vouloir d'une part augmenter les traitements/comportements sur ceux-ci et d'autre part d'augmenter les données traitées et cela sans modifier les programmes sources initiaux. Par exemple un composant `image` peut être soit un rectangle, soit un cercle que l'on peut afficher ou déplacer.

| rectangle | cercle | groupe
affiche | X | X |
déplace | X | X |
agrandit |  |  |

On peut vouloir étendre le composant `image` par le traitement `agrandir` et créer des groupes d'images. Le comportement des deux modèles diffère selon le sens de l'extension : données ou traitements. On définit tout d'abord, dans chaque modèle, la partie commune du composant `image`, puis on cherche à l'étendre.

### En fonctionnel

On définit le type image comme un type somme contenant deux cas. Les traitements prennent en entrée un paramètre de type image et effectuent le travail adéquat.

    # type image = Rect of float | Cercle of float ;;
    # let affiche = function Rect r -> ... | Cercle c -> ... ;;
    # let déplace = ... ;;

On encapsule ensuite ces déclarations globales dans un module simple.

#### Extension des traitements

L'extension des traitements dépend de la représentation du type image dans le module. Si celui-ci est abstrait, il n'y a plus de possibilité d'extension des traitements. Dans le cas où le type est resté concret, il est facile d'ajouter une fonction `agrandir` qui effectue le changement d'échelle d'une image en sélectionnant un rectangle ou un cercle par filtrage de motifs.

#### Extension des données

L'extension des données ne peut pas s'effectuer avec le type image. En effet les types Objective CAML ne sont pas extensibles, sauf pour le type exn représentant des exceptions. Il n'est pas possible d'étendre les données en conservant le même type, il est donc nécessaire de définir un nouveau type n_image de la manière suivante :

    type n_image = I of image | G of n_image * n_image;;

On doit redéfinir alors les traitements pour ce nouveau type en simulant une sorte d'héritage. Cela devient complexe dès qu'il y a plusieurs extensions.

### En objet

On définit les classes `rectangle` et `cercle`, sous-classes de la classe abstraite `image` qui a deux méthodes abstraites `affiche` et `deplace`.

    # class virtual image () =
       object(self:'a)
         method virtual affiche : unit -> unit
         method virtual deplace : float * float -> unit
       end;;
    # class rectangle x y w h =
       object
         inherit image ()
         val mutable x = x
         val mutable y = y
         val mutable w = w
         val mutable h = h
         method affiche () = Printf.printf "R: (%f,%f) [%f,%f]" x y w h
         method deplace (dx,dy) = x <- x +. dx; y <- y +. dy
       end;;
    # class cercle x y r =
       object
         val mutable x = x
         val mutable y = y
         val mutable r = r
         method affiche () = Printf.printf "C: (%f,%f) [%f]" x y r
         method deplace (dx, dy)  = x <- x +. dx; y <- y +. dy
       end;;

Le programme suivant construit une liste d'images et l'affiche.

    # let r = new rectangle 1. 1. 3. 4.;;
    val r : rectangle = <obj>
    # let c = new cercle 1. 1. 4.;;
    val c : cercle = <obj>
    # let l =  [ (r :> image); (c :> image)];;
    val l : image list = [<obj>; <obj>]
    # List.iter (fun x -> x#affiche(); print_newline()) l;;
    R: (1.000000,1.000000) [3.000000,4.000000]
    C: (1.000000,1.000000) [4.000000]
    - : unit = ()

#### Extension des données

Les données sont facilement extensibles en ajoutant une sous-classe à la classe `image` de la manière suivante.

    # class groupe i1 i2 =
       object
         val i1 = (i1:#image)
         val i2 = (i2:#image)
         method affiche () = i1#affiche(); print_newline (); i2#affiche()
         method deplace p = i1#deplace p; i2#deplace p
       end;;

On s'aperçoit alors que le <<type>> image devient récursif car la classe groupe dépend hors héritage de la classe image.

    # let g = new groupe (r:>image) (c:>image);;
    val g : groupe = <obj>
    # g#affiche();;
    R: (1.000000,1.000000) [3.000000,4.000000]
    C: (1.000000,1.000000) [4.000000]- : unit = ()

#### Extension des traitements

On définit une sous-classe abstraite d'image contenant une nouvelle méthode.

    # class virtual e_image () =
       object
         inherit image ()
         method virtual surface : unit -> float
       end;;

On peut définir les classes e_rectangle et e_cercle qui héritent de e_image et de rectangle et cercle respectivement. On peut alors travailler sur ces images étendues pour utiliser cette nouvelle méthode. Il subsiste une difficulté pour la classe groupe. Celle-ci possède deux champs de type image, donc même en héritant de la classe e_image il ne sera pas possible d'envoyer le message `agrandit` aux champs images. Il est donc possible d'étendre les traitements sauf pour les cas de sous-classes correspondant à des types récursifs.

### Extension des données et des traitements

Pour réaliser l'extension dans les deux sens, il est nécessaire de définir les types récursifs sous forme de classe paramétrée. On redéfinit la classe groupe.

    # class ['a] groupe i1 i2 =
       object
         val i1 = (i1:'a)
         val i2 = (i2:'a)
         method affiche () = i1#affiche(); i2#affiche()
         method deplace p = i1#deplace p; i2#deplace p
       end;;

On reprend alors le même principe que pour la classe e_image.

    # class virtual  ext_image () =
       object
         inherit image ()
         method virtual surface : unit -> float
       end;;
    # class ext_rectangle x y  w h =
       object
         inherit ext_image ()
         inherit rectangle x y w h
         method surface () = w *. h
       end;;
    # class ext_cercle x y  r=
       object
         inherit ext_image ()
         inherit cercle x y r
         method surface () = 3.14 *. r *.r
       end;;

L'extension de la classe groupe devient alors

    # class ['a] ext_groupe ei1 ei2 =
       object
         inherit image()
         inherit ['a] groupe ei1 ei2
         method surface () = ei1#surface() +. ei2#surface ()
       end;;

On obtient le programme suivant qui construit une liste `le` de type ext_image.

    # let er = new ext_rectangle 1. 1. 2. 4. ;;
    val er : ext_rectangle = <obj>
    # let ec = new ext_cercle 1. 1. 8.;;
    val ec : ext_cercle = <obj>
    # let eg = new ext_groupe er ec;;
    val eg : ext_rectangle ext_groupe = <obj>
    # let le = [ (er:>ext_image); (ec :> ext_image); (eg :> ext_image)];;
    val le : ext_image list = [<obj>; <obj>; <obj>]
    # List.map (fun x -> x#surface()) le;;
    - : float list = [8; 200.96; 208.96]

#### Généralisation

Pour généraliser l'extension des traitements il est préférable d'intégrer des fonctions dans une méthode `traitement` et de construire une classe paramétrée par le type du résultat du traitement. Pour cela on définit la classe suivante :

    # class virtual ['a]  get_image (f: 'b -> unit -> 'a) =
       object(self:'b)
         inherit image ()
         method traitement () = f(self) ()
       end;;

Les classes suivantes possèdent alors un paramètre fonctionnel supplémentaire pour la construction de leurs instances.

    # class ['a] get_rectangle f x y  w h =
       object(self:'b)
         inherit ['a] get_image f
         inherit rectangle x y w h
         method get = (x,y,w,h)
       end;;
    # class ['a] get_cercle f x y  r=
       object(self:'b)
         inherit ['a] get_image f
         inherit cercle x y r
         method get = (x,y,r)
       end;;

L'extension de la classe groupe prend alors deux paramètres de type :

    # class ['a,'c] get_groupe f eti1 eti2 =
       object
         inherit ['a] get_image f
         inherit ['c] groupe eti1 eti2
         method get = (i1,i2)
       end;;

On obtient le programme qui étend le traitement des instances de get_image.

    # let etr = new get_rectangle
       (fun r () -> let (x,y,w,h) = r#get in w *. h) 1. 1. 2. 4. ;;
    val etr : float get_rectangle = <obj>
    # let etc = new get_cercle
       (fun c () -> let (x,y,r) = c#get in 3.14 *. r *. r) 1. 1. 8.;;
    val etc : float get_cercle = <obj>
    # let etg = new get_groupe
       (fun g () -> let (i1,i2) = g#get in i1#traitement() +. i2#traitement())
       (etr :> float get_image) (etc :> float get_image);;
    val etg : (float, float get_image) get_groupe = <obj>
    # let gel = [ (etr :> float get_image) ; (etc :> float get_image) ;
                 (etg :> float get_image) ];;
    val gel : float get_image list = [<obj>; <obj>; <obj>]
    # List.map (fun x -> x#traitement()) gel;;
    - : float list = [8; 200.96; 208.96]

L'extension des données et des traitements est plus facile dans le modèle objet en s'appuyant sur le modèle fonctionnel.