Précédent Index Suivant

É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.


Précédent Index Suivant