Cpatterns
Cpatterns
Notes de cours
Christophe Dony
1 Introduction
Idée
— Architecture - génie civil (Christopher Alexander) : Each pattern describes a problem which occurs over and over
again in our environment, and then describes the core of the solution to that problem, in such a way that you can
use this solution a million times over, without ever doing it the same way twice.
— Informatique - génie logiciel
livre de base : [GHJV 94] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides Design Patterns : Elements
of Reusable Object-Oriented Software Addison Wesley, 1994.
— Le livre en ligne : [Link]
— les schémas du livre en ligne : [Link]
Définition :
— Un Schéma de conception nomme, décrit, explique et permet d’évaluer une conception d’un système exten-
sible et réutilisable digne d’intérêt pour un problème récurrent.
Intérêts
Paramétrage, Variabilité
Les schémas offrent des solutions génériques paramétrées selon les schémas par spécialisation et composition présentés
dans la première partie de ce cours.
1
Ce qui varie Schéma
Algorithmes Strategy, Visitor
Actions Command
Implementations Bridge
Réponse aux changements Observer
Interaction entre objects Mediator
Création des objets Factory, Prototype
Création des données Builder
Traversal algorithm Visitor, Iterator
Object interfaces Adapter
Object behaviour Decorator, State
2
6 private Singleton() {}
3
Discussions
1. Commentaire : empêcher les instantiations par les clients : visibilité du constructeur,
1 private Singleton() {}
1 class iSingleton{
2 static iSingleton ∗INSTANCE;
3 public:
4 iSingleton() {
5 INSTANCE=this;
6 }
4. Alternative : si ∃ métaclasse Class et si new est une méthode ( exemples : Pharo, Smalltalk, Python, Ruby, ...),
Redéfinir new sur chaque classe de classe Singleton :
Listing (3) – Singleton,via une redéfinition de la méthode new. INSTANCE est toujours un attribut de classe
(static)
Listing (4) – Singleton, via une nouvelle méta-classe Singleton-class, la classe des classes qui ne peuvent
avoir qu’une seule instance. Elle possède un attribut de méta-classe Unique-Instance. Version Common-Lisp
4
Listing (5) – Spécialisation de l’instantiation sur singleton-class, (call-next-method’) réalise l’appel de la
méthode masquée par la redéfinition (envoi de message à super).
6. Exemple : Utilisation du schéma Singleton pour les classes True et False, possible discussion sur l’implantation
des structures de contrôle dans le monde objet et l’intérêt des fermetures lexicales.
3.1 Problème
Ajouter dynamiquement une fonctionnalité à un objet (individuel), sans modifier ni sa classe, ni donc les autres instances
de sa classe.
Application possible : Ajouter des décorations (“barre de scroll”, “bordure”) à un objet graphique (Figure 2).
drawScrollBar() drawBorder()
scrollTo()
SrollBorduredTextView
{[Link]();
draw() [Link]();
[Link]();}
Figure (2) – Décoration d’une textview, solution universelle avec héritage multiple. Limitations : a) statique
b) autant de sous-classes que de combinaisons potentielles de décorations.
5
ComposantVisuel
#draw()
composant
TextView Decorateur
ScrollDecorateur BordureDecorateur
Un BorderDecorateur
composant Un ScrollDecorateur
Figure (4) – Objets représentant une Textview décorée selon le schéma “Decorateur”.
6
3.3 Principe Général de la solution (figure 5)
est−décoré−par
Composant
1
operation()
décoré
ComposantConcret Decorateur
1
operation() operation() {décoré.operation();}
DecorateurConcretA DecorateurConcretB
Figure (5) – Composant : objet métier quelconque à décorer, exemple : textView. Décorateur : objet
décorant (donc ajoutant des fonctionnalités) à un objet métier, exemple : scrollDecorator).
7
1 public class BorderDecorator extends Decorateur {
2 // ajoute une bordure à un composant graphique
1 class Application {
2 public static void main(String[] args){
3 Composant c = new VueTexte(’’Hello’’);
4 //ajoûter une décoration
5 c = new BorderDecorator(c, 2.5);
6 [Link](); //dispatch 1, sélection selon la décoration
3.5 Discussions
1. Peut-on réaliser un décorateur en Javascript avec le lien proto : oui.
L’héritage entre objets, via le lien proto ouvre de nouvelles possibilités de décoration ;
8
1 var User = function (name,number) {
2 [Link] = name;
3 [Link] = number;
4 [Link] = function () {
5 return("User: " + [Link]); }; }
9 var personne2 = {
10 __proto__: personne1, //lien proto induisant un héritage entre objets
11 prenom: "Paul",
12 };
14 [Link](); // Jean
15 [Link](); // Paul ... héritage de getPrenom() et liaison dynamique
16 [Link]("Martin");
17 [Link](); // Martin
18 [Link](); // ????
2. Problème : nécessité pour un décorateur d’hériter de la classe abstraite Composant et donc de redéfinir toutes les
méthodes publiques pour réaliser une redirection de message.
3. Problème : poids des objets : il est recommandé de ne pas définir (trop) d’attributs dans la classe abstraite composant
afin que les décorateurs restent des objets “légers”.
Une solution à ce problème est de modifier le pattern en remplaçant la classe abstraite Composant par une interface,
et donc le lien sous-classe-de entre Décorateur et Composant par un lien implémente.
4. Problème : incompatibilité potentielle de différentes décorations.
5. Problème : L’ordre d’ajout des décorations est signifiant.
6. commentaire : un décorateur peut être vu comme un composite avec un seul composant, mais le décorateur a des
fonctionnalités propres que n’a pas son composant ...
3.6 Double liaison (double dispatch) : appeler la bonne méthode en présence de multiples
affectations polymorphiques
separation of responsibility for type-specific logic
Le schéma “décorateur” permet que les décorations soient de différentes sortes mais également que les objets décorés
puissent l’être, en fournissant différentes implantations de la méthode draw(), si l’on garde l’exemple utilisé en section 3.
9
L’envoi de message réalise une sélection de méthode selon le type dynamique du receveur, mais pas selon le type dynamique
des autres paramètres (sauf en Common-Lisp-Object-System).
Le schéma “double-dispatch” stipule qu’il est nécessaire dans une application d’avoir un envoi de message pour chaque
type statique utilisé.
Exemple : différents décorateurs sur différentes sortes de textView.
separation of responsibility for type-specific logic
Problème
Intégrer dans une application client une instance d’une classe existante dont l’interface ne correspond pas à la façon dont
le client doit l’utiliser.
Fait intervenir les Participants suivants :
Cible : objet définissant le protocole commun à tous les objets manipulés par le client, (dans l’exemple : shape)
Client : objet utilisant les cibles (l’éditeur de dessins)
Adapté : l’objet que l’on souhaite intégrer à l’application
Adapteur : objet réalisant l’intégration.
uses
DrawingEditor DrawingShapes TextView
10
4.2 Principe général de la solution : figures 7 et 8
requete() requeteSpecifique()
Adapteur
Requete() return([Link]())
requete() requeteSpecifique()
adapté
Adapteur
Requete() return(adapté.requeteSpecifique())
4.3 Discussion
1. Application à la connexion non anticipée de composants. Voir cours “[Link]”.
Exemple, intégrer un compteur (unCompteur instance de Compteur définie dans une bibliothèque) dans une appli-
cation graphique existante.
Problème : la classe Compteur n’implante pas l’interface MouseListener.
1 [Link](new [Link]() {
2 public void mouseClicked([Link] evt) {
3 [Link]();
4 }
5 });
Listing (6) – Solution Java : adaptation d’un objet externe via une classe anonyme dotée d’une méthode
conforme au client,réalisant la redirection de message
11
2. Mise en évidence du problème posé par la redirection de message ici liée à l’utilisation de la composition en
paliatif de l’héritage multiple :
— Nécessité de redéfinir sur l’adapteur toutes les méthodes publiques de l’adapté.
— Redirection de message : perte du receveur initial (cf. fig. 9). initial du message est perdu, ceci rend certains
schémas de réutilisation difficiles à appliquer.
uses
DrawingEditor DrawingShapes TextView
{return(new TextView().boundingBox());
1.
Figure (9) – Adaptation par composition dans l’exemple de l’éditeur de dessin ; receveur initial perdu dans la
méthode boundingBox(). Ceci rend en l’état impossible la prise en compte d’une spécialisation dans l’application
de la méthode plugin de la classe adaptée TextView. La figure 10 propose un schéma global de solution à ce
problème.
uses
DrawingEditor DrawingShapes TextView
MyTextView
plugin()
{return(new MytextView().boundingBox());
2.
Figure (10) – Adaptation par composition et solution partielle au problème de perte du receveur initial.
12
La “redirection de message” et la “perte du receveur initial” associée apparaissent dans de nombreux schémas
utilisant la composition ...
dont ... dont Adapter, Proxy, State .
1 package main
Figure (11)
13
1 type Button struct {
2 Label // Embarquement (héritage matérialisé par un attribut sans nom)
3 }
4 func (b Button) Click() {
5 [Link]("%p:[Link]\n", &b) }
6 func (b Button) KindOf() string {
7 return "Button" }
9 func main() {
10 label := Label{Widget{10, 10}, "Joe"}
11 [Link]() //un ”Label” : ”Joe” 10 10
Listing (8) – Point clé : appeler “[Link]” donne le même résultat que “[Link]()” ... le
receveur passé à la méthode Paint est le Label, pas le Button
imp
Window WindowImp
drawText() drawText()
drawRect() drawLine()
{repeat 4
[Link](dir); dir += 90;}}
XWindowImp MacWindowImp
IconWindow TranscientWindow
drawText() drawText()
drawBorder() drawCloseBox() drawLine() drawLine()
5.2 Discussion
— Implantation : Qui décide des types de classes d’implantation créer ? Constructeur avec argument (lien à faire avec
l’utilisation d’un framework paramétré par composition). Test dynamique selon critère. Délégation du choix à un
objet externe, lien avec une fabrique (schéma Factory).
14
— Bridge évite la création de hiérarchies multi-critères mélant les classes conceptuelles et les classes d’implémentation.
— Les concepts et les implantations sont extensibles par spécialisation de façon indépendantes.
— Les clients sont indépendants de l’implantation (Il est possible de changer une implantation (recompilation) sans
que les clients n’en soient affectés).
— L’idée liée à celle d’interface (à la Java), alternative autorisant la définition de méthodes dans la hiérarchies des
concept.
etatCourant
Object Etat
requete() requete()
requete() requete()
{...;
[Link](...);
15
6.2 Exemple d’application : implantation d’une calculatrice
enter(String) enter(String)
enter(String)
7 public Calculette(){
8 etats[0] = new ENombre1(this);
9 etats[1] = new EOperateur(this);
10 etats[2] = new ENombre2(this);
11 etatCourant = etats[0];
12 accumulateur = 0; }
13 ...
14 // accesseurs lecture/écriture pour ‘‘accumulateur’’ et pour ‘‘operateur’’
15 ...
16 //obtention du résultat
17 public double getResult() { return accumulateur; }
16
Les états sont invisibles aux clients
Calculette dans état initial, dans l’attente de l’entrée d’un premier opérande
3 ENombre1(Calculette c) { super(c); }
17
5 float temp = 0;
6 try {temp = [Link](s);}
7 catch (NumberFormatException e) {
8 throw new CalculetteNumberException(s);}
10 switch ([Link]([Link]())) {
11 case plus: [Link]([Link]() + temp); break;
12 case mult: [Link]([Link]() ∗ temp); break;
13 default:
14 throw new CalculetteUnknownOperator([Link]());}
15 return (1);}}
6.3 Discussion
1. Implémentation : comment représenter l’état courant ?
2. Ce schéma rend explicite dans le code les changements d’état des objets : applications pour la sécurité (contrôle du
bon état interne des objets) et la réutilisation (exercice : passer d’une calculette infixée à une postfixée).
3. Evolution des Langages : constructions prenant en compte la catégorisation selon l’état (exemple : la définition par
sélection de Lore).
14 vieillir
15 age := age + 1.
16 (age = 18) ifTrue: [ Majeur adoptInstance: self ]! !
18
Figure (15) – Une implantation du schéma “State”, utilisant la possibilité pour une instance de changer
dynamiquement de classe.
— Apparté : application du changement dynamique de classe ; le cas de la mise au point de programmes à exécution
continue (Dynamic Software Update)
19
Figure (17) – Partage de valeur entre objets en programmation par prototypes, application aux points de vues.
- Daniel Bardou and Christophe Dony. Split Objects : a Disciplined Use of Delegation within Objects. October
1996.
7.1 Problème
Faire qu’un objet devienne un observateur d’un autre afin qu’à chaque fois que l’observé est modifié, l’observateur soit
prévenu.
Exemple d’application :
- Abonnements, toutes formes de “publish/subscribe”,
- connexion non anticipée de composants
- IHM (MVC).
20
7.2 Principe général de la solution (Figure 18)
Figure (18) – Le schéma Observer permet d’établir une collaboration entre un observateur et un observé (ou
“sujet” selon ce diagramme).
7.3 Discussion
— MVC, Event-Based, ...
— Analyser la conception du découplage : [Link]()
— Mémorisation des écouteurs et “garbage collector”.
— Les limites de la séparation des préoccupations (separation of concerns) selon le schema Observateur : comment
enlever les [Link]() du code métier.
— Observer : Classe abstraite versus interface.
7.5 Architecture
Trois hiérarchies de classes définissent les modèles, les vues et les contrôleurs.
1. Krasner Pope tutorial on MVC : [Link]
21
– Tout modèle (objet métier) est défini par une sous-classe de la classe Model. Un modèle peut avoir des observateurs (en
premier lieu des vues).
– Tout contrôleur est définie par une sous-classe de Controller, chaque contrôleur connait son modèle (et si on le souhaite
sa vue), le contrôleur observe l’utilisateur.
– Toute vue est définie par une sous-classe de View. Chaque vue connait son contrôleur et son modèle.
Controleur Vue1
Capte Evenement Affiche état modèle
demande MAJ
Classe du framework
Classe du framework
22
Classe d’une l’application
4 protected changerValeur(i){
5 valeur = valeur + i;
6 [Link]("valeur");}
Classe du framework
Classe du framework
23
Classe d’une application
8 Généralisation, Spécialisation
L’utilisation des schémas de conception s’est généralisée, comme en témoigne par exemple la bibliothèque en ligne “Portland
Pattern Repository” ().
Les langages de schémas (ensemble de schémas traitant d’une question globale et faisant référence les uns aux autres) se
sont spécialisés :
— reengineering patterns
— Architectural pattern in computer science
— Interaction patterns
— .̇.
et encore plus spécifiquement :
— exception handling patterns
— user interface patterns,
— .̇.
24