Navigation

Patterns GoF : Structuraux

Un article de ODcWiki.

Jump to: navigation, search
Image:Auteur.png Auteur : scogneau.


L'objet de cet article est de présenter rapidement les patterns structuraux et quelques exemples d'utilisation. Les patterns présentés sont les suivants : Adapter (Adaptateur), Bridge (Pont), Composite, Decorator (Decorateur), Facade, Flyweight et Proxy.

Sommaire

Adapter

L'adaptateur va permettre d'introduire un héritage entre deux objets ayants des interfaces différentes , la classe qui permet de faire le lien est l'adaptateur. L'adaptateur va permettre de rediriger des appels de méthodes vers l'objet à adapter. Ainsi, si on considère un framework de dessin qui permet d'afficher des formes géométriques, d'écrire du texte, de dessiner des courbes à main levée... en définissant une interface différente pour chacun des types d'objet à afficher. L'application cliente du framework peut souhaiter n'utiliser qu'une seule interface pour manipuler ces objets, il sera intéressant d'utiliser un adaptateur permettant de faire le lien entre l'interface utilisable par l'application et les objets du framework. Cette approche va permettre d'ajouter une famille d'éléments facilement (soit en utilisant un autre framework, soit un définissant cette famille dans l'application) et de se protéger facilement des variations du framework en ajoutant une couche d'abstraction entre le framework et l'application.

Image:Adaptateur.jpg Diagramme de classe de l'adaptateur.

Bridge

Le bridge permet de faire le lien entre une abstraction et une implémentation sans passer par de l'héritage, l'héritage entraînant un couplage fort entre les éléments alors qu'il est parfois préférable d'avoir un couplage faible. Le bridge permet d'avoir un couplage faible entre les éléments . Le bridge implémente l'abstraction et fait le lien avec les implémentations, il permet de masquer l'interface de l'abstraction qu'il implémente aux différents objets qu'il manipule.Le bridge pourrait s'utiliser pour écrire un gestionnaire de fenêtre multiplateforme. Ce gestionnaire décrira une série de comportements communs qui nécessiteront des appels spécifique à un framework d'affichage (API Windows, GTK,Qt...) , l'appel à ces frameworks pourra être fait dans des bridges.

Image:Bridge.jpg Diagramme de classe du bridge.

Composite

Le pattern composite est utilisé pour représenter un arbre , il repose sur l'abstraction du comportement des noeuds et des feuilles : les noeuds et les feuilles vont hériter de la même classe mère afin de manipuler l'un ou l'autre indifféremment. Cette classe mère va permettre de définir une famille d'objets composites, de plus elle aura la responsabilité de gérer le comportement des composites. Cette abstraction permet de rajouter très facilement de nouveaux types de noeuds ou feuilles en héritant de cette classe. Cette stratégie d'héritage ne permet pas de vérifier la compatibilité entre les noeuds et les feuilles (si deux familles héritent de la même classe mère on ne souhaitera probablement avoir un arbre mélangeant les deus familles) et cela imposera une vérification du type de composite manipulé au runtime. Les objets gigognes sont des bons exemples de composites (les poupées russes par exemple).

Image:Composite.jpg Diagramme de classe du composite.

Decorator

Le pattern décorateur est utilisé pour rajouter un comportement sur un objet dynamiquement. Un décorateur implémente l'interface du composant que l'on souhaite utiliser et contient une référence vers l'objet à décorer, ainsi la décoration va consister à appeler le traitement souhaité puis appelé l'opération initiale sur l'objet à décorer. Il permet de modifier le comportement d'un objet sans l'impacter, de plus la modification de comportement pourra n'être utilisé que lorsque elle est nécessaire. Le décorateur permet d'avoir une forte modularité permettant de gérer un grand nombre de combinaison de fonctionnalités pour un objet. Cependant il ne faut pas tomber dans l'excès : un nombre élevé de petits objet sera difficile à maintenir et à utiliser. L'exemple le plus courant sur le décorateur est la gestion d'une fenêtre : affichage de celle ci avec des bordures, des scrollbars... Ce genre de problème peut se régler par de l'héritage ainsi on pourrait avoir une classe BorderScrollBarWindow qui hérite de ScrollbarWindow qui hérite de Window. L'approche utilisé par le décorator va découpler ces fonctionnalités pour avoir une meilleur modularité, ainsi on aura :

  • une classe WindowDecorator qui héritera de Window et qui aura un attribut Window
  • une classe BorderWindowDecorator qui permettra de dessiner des bordures
  • une classe ScrollBarWindowDecorator qui gérera les scrollbars.

Toutes ces classes vont pouvoir se combiner pour former une fenêtre avec des scrollbar et des bordures.

ce qui peut donner par exemple

public interface Window{
	
	public void afficher();
}
 
public class WindowDecorator implement Window{
	
	private Window window;
 
	public void afficher();
}
 
public class BorderWindowDecorator implement WindowDecorator{
	
	public void afficher(){
		afficherBordure();
		afficher();
	}
 
	private afficherBordure();
}

Un autre exemple pourrait être de rendre une méthode transactionnelle : on peut par exemple définir un transactionMethodDecorator qui aura la charge de démarrer une transaction avant l'appel de la méthode puis de terminer la transaction après l'appel.

Image:Decorateur.jpg Diagramme de classe du decorateur.

Façade

Le pattern facade permet de présenter un ensemble de fonctionnalité tout en masquant la complexité : un appel de facade pourra déclencher l'appel à plusieurs traitement sans que le client ne le sache. Ce pattern permet une protection des clients de la facade contre les modifications pouvant être faite dans les services.

On utilisera donc ce pattern lorsque l'on veut fournir une interface simple face à un système complexe, ou que l'on veut découpler le client et le système pour avoir des modules moins dépendant les uns des autres. On peut aussi utiliser ce pattern pour distinguer plusieurs modules qui auront une faible dépendances les uns envers les autres car il ne communiqueront que par leurs facades respectives.

Ce pattern va donc entraîner un couplage faible entre les deux sous système lié par la facade. On peut utiliser ce pattern pour définir les services disponibles sur un serveur pour les clients distants.


Image:Facade.jpg Diagramme de classe de la facade.

Flyweight

Le flyweight est un objet instancié une seule fois alors que l'on a besoin d'un grand nombre d'instance de ces objets et que ces instances contiennent beaucoup d'informations similaires. On va chercher à externaliser tous les éléments variables de l'objets afin que le contexte dans lequel on les manipule puissent les modifier (l'objet flyweight doit être utilisable indépendamment du contexte).

On peut utiliser ce pattern lorsque toutes les conditions suivantes sont réunis :

  • On utilise un grand nombre d'instance de cet objet
  • La persistance des objets va nécessiter beaucoup d'espace disque.
  • La plupart des états de l'objet peuvent être externalisé
  • Des grappes d'objets volumineuse pourront être replacé par quelques objets partagés en dehors du contexte.
  • On ne fait pas de test sur l'identité de l'objet.

L'utilisation de ce pattern va donc entrainer une réduction du nombre d'instance de cet objet dans l'application et un gain pour la persistance mais on pourra avoir des pertes de performances au runtime. En effet il faudra retrouver l'instance de l'objet que l'on souhaite utiliser à chaque fois et lancer les calculs qui ne sont plus de la responsabilité de l'objet.

Un bon exemple est éditeur graphique les forme géométriques de base peuvent être des Flyweight et être modifié par le contexte dans lequel ils sont utilisé. Le graphique pourra avoir enregistré qu'il contient trois formes avec leurs coordonnées et un facteur d'échelle représentant la taille, pour s'afficher il lui suffira de rechercher la forme adéquat et de l'afficher au bon endroit et à la bonne taille.

Image:Flyweight.jpg Diagramme de classe du Flyweight.

Proxy

Le pattern proxy est utilisé lorsque l'on contrôler l'accès à un objet ou pour le remplacer. Le contrôle d'accès peut avoir différentes applications : compter le nombre d'objet utilisant l'objet "proxifié" pour pouvoir le supprimer de la mémoire (via garbage collector en java, par un free en C++). Le proxy va se comporte comme l'objet qu'il répresente et à la responsabilité de l'instancier. Une utilisation possible est le chargement d'image dans un document texte : un document peut contenir un grand nombre d'image pour des raisons de performances il est préférable de ne pas toutes les charger en mémoire, vu de l'éditeur le proxy va se comporter comme l'image et il ne chargera l'image que lorsque on souhaitera l'afficher à l'écran par exemple. Un exemple plus familier des développeurs Java concerne Hibernate : lorsque l'on charge une entité composé d'autre entité et d'attribut de type primitif on peut constater que les attribut entités sont chargés dans des proxy et que le chargement effectif de l'attribut sera fait lorsque l'on souhaitera le lire réellement.

Image:Proxy.jpg Diagramme de classe du Proxy.

Boîte à outils
Dernière modification de cette page le 25 janvier 2010 à 10:04