Design pattern comportementaux PDF
Document Details
Uploaded by HotDandelion
Sorbonne Université / LIP6 - INRIA
Jonathan Lejeune
Tags
Summary
Ce document présente des design patterns comportementaux. Il couvre des concepts comme la programmation objet, les schémas UML, et des exemples de design patterns spécifiques comme Template Method, Strategy, State, et Command.
Full Transcript
Design pattern comportementaux Jonathan Lejeune Programmation Objet Sorbonne Université/LIP6-INRIA sources et images - refactoring.guru - cours précédents de...
Design pattern comportementaux Jonathan Lejeune Programmation Objet Sorbonne Université/LIP6-INRIA sources et images - refactoring.guru - cours précédents de Y. Thierry-Mieg © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 1 / 38 Les Designs Patterns comportementaux Principes généraux Objectif : Communication et affectation des responsabilités entre objets Focus : méthodes offertes et leurs implémentations Patterns comportementaux abordés Cours collections : Iterator Ce cours : Command , State, Strategy , Visitor , TemplateMethod Cours java FX : Observer Patterns comportementaux non abordés ChainOfResponsibility, Interpreter, Mediator, Memento © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 2 / 38 Template Method Crédit image : refactoring.guru © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 3 / 38 Scénario illustratif de la problématique On souhaite programmer une application de fouille de données parmi des documents word (.doc) Écrire une classe qui offre une méthode mine(DocFile) dont les étapes sont 1) ouvrir le fichier 2) Extraire et parser les données du fichier doc 3) analyser les données 4) faire un rapport 5) fermer le fichier On souhaite maintenant traiter des fichiers PDF Refaire une autre classe offrant la même méthode mine(PdfFile) mais en modifiant l’étape 2 On souhaite maintenant traiter des fichiers CSV Refaire encore une autre classe offrant la même méthode mine(CsvFile) mais en modifiant l’étape 2 Une analyse se base sur un ensemble de fichiers ayant des formats différents (CSV, PDF, DOC, XML, JSON,...) © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 4 / 38 Le design-pattern Template Method Définition Définir un squelette d’un algorithme dans une superclasse en laissant les sous-classes redéfinir les différentes étapes de l’algorithme sans changer sa structure Étapes de mise en place 1) Extraire les différentes étapes de l’algorithme à implanter 2) Créer une classe offrant une méthode (template) finale qui implante la structure globale de l’algorithme 3) Dans cette même classe définir une méthode par étape de l’algorithme 4) Pour chaque variante de l’algorithme, créer une sous classe qui redéfinit les étapes nécessaires © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 5 / 38 Le design-pattern Template Method : schéma UML MotherClass + templateMethod(p:Param): Res step1() + step1(p:P1) : Res1 if(step2()){ + step2(p:P2) : Res2 step3() + step3(p:P3) : Res3 }else{ + step4(p:P4) : Res4 step4() ; } ConcreteClass1 ConcreteClass2 + step3(p:P3) : Res3 + step1(p:P1) : Res1 + step4(p:P4) : Res4 + step2(p:P2) : Res2 + step3(p:P3) : Res3 + step4(p:P4) : Res4 © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 6 / 38 Le DP Template Method : implantations concrètes Les collections java Toutes les méthodes concrètes de AbstractCollection, AbstractList, AbstractMap et AbstractSet Exemples pour AbstractCollection : isEmpty utilise size() contains utilise iterator addAll utilise iterator et add Les flux d’entrée/sortie java IntputStream : Template method : int read(byte b[], int off, int len) méthode abstraite : int read() OutputStream : Template method : void write(byte b[], int off, int len) méthode abstraite : void write(int b) © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 7 / 38 Strategy Crédit image : refactoring.guru © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 8 / 38 Scénario illustratif de la problématique On souhaite programmer une application de calcul d’itinéraires rou- tiers sur une carte. Offrir une méthode Path computeRoute(Point dep, Point dest) On souhaite avoir la possibilité d’avoir des itinéraires à pied en passant par des chemins non accessibles en voiture Faire un if-else dans la méthode computeRoute On rajoute le fait de pouvoir être en vélo en privilégiant les pistes cyclables Faire un if-else if-else dans computeRoute On veut ajouter des options d’itinéraire (points touristiques, pas de péage, utilisation des transports en communs,...) © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 9 / 38 Le design-pattern Strategy Définition Définir une famille d’algorithmes, les encapsuler, et les rendre interchangeables. Étapes de mise en place 1) Repérer dans une classe X une fonctionnalité dont l’algorithme peut changer 2) Déclarer une interface S commune à tout algorithme 3) Implanter chaque algorithme dans sa propre classe 4) Dans la classe X ajouter un attribut vers S et déléguer à cet attribut tout comportement dépendant de la stratégie 5) Permettre au client de changer dynamiquement l’algorithme voulu © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 10 / 38 Le design-pattern Strategy : schéma UML Strategy s = new ConcreteStrategy() Client context.setStrategy(s) Context.doSomethg(...) ContextX - strat Strategy + setStrategy(s:Strategy) : void + doSomethg(param : p):Res + func(p:Param):Res Strat.func(..) ConcreteStrategyA ConcreteStrategyB + func(p:Param):Res + func(p:Param):Res ConcreteStrategyC + func(p:Param):Res © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 11 / 38 Le design-pattern Strategy Comment faire pour que la stratégie puisse accéder aux éléments du contexte ? Solution 1 La stratégie possède un attribut référençant le contexte Le contexte expose publiquement des accesseurs sur les états nécessaires à la stratégie ⇒ encapsulation affaiblie Solution 2 Les méthodes de la stratégie ont en argument les états du contexte nécessaires © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 12 / 38 Le design-pattern Strategy : exemple de contexte public class Scheduler { private List < Task > tasks = new ArrayList < >() ; private S c h e d u l e r S t r a t e g y strat = null ; public Scheduler ( S c h ed u l e r S t r a t e g y ss ) { this. strat = ss ; } public void addNewTask ( Task t ) { tasks. add ( t ) ; } public Task getNextTask () { return strat. nextTask ( tasks ) ; } } © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 13 / 38 Le design-pattern Strategy : exemple de stratégies public interface S c h e d u l e r S t r a t e g y { public Task nextTask ( List < Task > tasks ) ; } public class Fifo implements S c h e d u l e r S t r a t e g y { public Task nextTask ( List < Task > tasks ) { return tasks. remove (0) ; } } public class Priority implements S c h e d u l e r S t r a t e g y { public Task nextTask ( List < Task > tasks ) { if ( tasks. size () ==0) return null ; int iwin = 0; for ( int i = 0; i < tasks. size () ; i ++) { Task cur = tasks. get ( i ) ; Task win = tasks. get ( iwin ) ; if ( cur. prio () > win. prio () ) iwin = i ; } return tasks. remove ( iwin ) ; } } © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 14 / 38 Le design-pattern Strategy : synthèse Avantages Le comportement d’une méthode peut être changé dynamiquement Isolation du code d’un algorithme du code qui l’utilise On peut ajouter des stratégies sans remettre en cause l’existant Limites Peu utile si l’algorithme ne change pas Le client doit connaître les différentes stratégies pour paramétrer le contexte L’ajout de classe peut être obsolète si le langage offre des lambda expressions © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 15 / 38 State Crédit image : refactoring.guru © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 16 / 38 Problématique principale du DP State Comment implanter de manière modulaire une machine à état ? © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 17 / 38 Le design-pattern State Définition Pouvoir changer le comportement d’un objet quand son état change, sans pour autant en changer l’instance Étapes de mise en place 1) Séparer ce qui fera office de contexte et office d’état 2) Déclarer une interface S d’état qui offre les méthodes dont le comportement dépendra de l’état du contexte X 3) Pour chaque état possible, déclarer une classe qui implante l’interface 4) Dans la classe X ajouter un attribut de type S et déléguer les méthodes de X à l’attribut. 5) Ajouter une référence pour chaque état vers le contexte. Le changement d’état peut être fait par le client, le contexte ou bien une classe d’état. © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 18 / 38 Le design-pattern State : schéma UML Client ContextX c = new ContextX(); changeState(initialState) c.changeState(new ConcreteState(c)) ContextX - state State + ContextX() + changeState(n : state) + doThis() + doThis() - context + doThat() + doThat() state.doThis(..) ConcreteStateB ConcreteStateC ConcreteStateA + doThis() + doThis() + doThat() + doThat() + doThis() + doThat() State other = new OtherState(context); context.changeState(other) © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 19 / 38 Le design-pattern State : exemple Modélisation d’une porte de métro close() lock() Opened Closed pushButton() pushButton() Locked unlock() lock() © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 20 / 38 Le design-pattern State : exemple Le contexte public class Door { private DoorState state = new Closed ( this ) ; public void changeState ( DoorState s ) { this. state = s ;} public boolean pushButton () { return state. pushButton () ; } public boolean close () { return state. close () ; } public boolean lock () { return state. lock () ;} public boolean unlock () { return state. unlock () ; } } La classe State public abstract class DoorState { protected final Door door = null ; public DoorState ( Door context ) { this. door = context ; } public abstract boolean pushButton () ; public abstract boolean lock () ; public abstract boolean unlock () ; public abstract boolean close () ; } © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 21 / 38 Le design-pattern State : exemple (les états concrets) L’état concret Locked public class Locked extends DoorState { public Locked ( Door d ) { super ( d ) ;} public boolean pushButton () { return false ;} public boolean lock () { return true ;} public boolean unlock () { door. changeSate ( new Closed ( door ) ) ; return true ; } public boolean close () { return false ;} } L’état concret Opened public class Opened extends DoorState { public Closed ( Door d ) { super ( d ) ;} public boolean pushButton () { return true ; } public boolean lock () { return false ;} public boolean unlock () { return false ;} public boolean close () { door. changeSate ( new Closed ( door ) ) ; return true ; } } © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 22 / 38 Le design-pattern State : exemple (les états concrets) L’état concret Closed public class Closed extends DoorState { public Closed ( Door d ) { super ( d ) ;} public boolean pushButton () { door. changeSate ( new Opened ( door ) ) ; return true ; } public boolean lock () { door. changeSate ( new Locked ( door ) ) ; return true ; } public boolean unlock () { return false ;} public boolean close () { return true ;} } © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 23 / 38 Command Crédit image : refactoring.guru © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 24 / 38 Scénario illustratif de la problématique On doit créer une barre de menu d’un éditeur graphique avec un ensemble de boutons offrant différentes opérations Créer une classe générique Button que l’on peut utiliser aussi bien dans la barre de menu qu’ailleurs. Implanter des sous-classes de Button et y définir l’action à effectuer lorsque l’on clique dessus. Le code graphique et le code métier sont fortement dépen- dant l’un de l’autre. On suppose que l’on ne fera plus de modification de la classe Button par la suite On se rend compte que les fonctionnalités des boutons peuvent être aussi appelées via des raccourcis clavier ou via des menus contextuels On duplique le même code dans différents contrôles On s’est rendu compte qu’il y avait un bug dans le code © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 25 / 38 Le design-pattern Command Définition Considérer des requêtes à un objet receveur comme des objets pleinement autonomes permettant de planifier et/ou d’annuler leurs exécutions. Étapes de mise en place 1) Déclarer une interface Command avec une seule méthode (ex : run()) 2) Définir chaque type de requête/commande en une classe à part entière implantant l’interface. 3) Pour chaque commande concrète, ajouter en tant qu’attribut ses arguments et une référence vers le receveur 4) Créer une classe Sender qui planifie et invoque les commandes au receveur 5) Le client créé le receveur, les commandes et l’envoyeur © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 26 / 38 Le design-pattern Command : schéma UML Sender + addCommand(c:Command) * + executeCommands() Command + executeCommand(s:String) -commands + execute() + undo() Client CommandA - params -receiver + CommandA(params,receiver) + execute() + undo() CommandB Receiver - params + operation(a,b,c) -receiver + CommandB(params,receiver) + execute() + undo() © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 27 / 38 Le design-pattern Command : exemple © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 28 / 38 Le DP Command : implantation concrète en Java Les Runnable Type abstrait des commandes : interface Runnable ⇒ offre une seule méthode void run() Senders : Thread : exécute le runnable dans un nouveau flux d’exécution Timer : exécute le runnable à une certaine date © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 29 / 38 Visitor Crédit image : refactoring.guru © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 30 / 38 Scénario illustratif de la problématique On veut exporter sous format XML un énorme graphe structurant des informations géographiques. Il existe plusieurs classes de nœuds (maga- sin, industrie... ) et avec plusieurs niveaux de granularité (bâtiment, quartier, ville... ) On ajoute une méthode exportToXML à la classe Node et on re- définie cette méthode dans chaque classe fille Finalement on veut aussi exporter au format JSON On refait la même chose en remodifiant toutes les classes Le commercial a aussi vendu le fait de pouvoir exporter en YAML On refait la même chose en remodifiant de nouveau toutes les classes L’architecte logiciel de l’application ne vous donne pas son accord de modifier les classes car le code est déjà en pro- duction et on risque d’introduire des bugs © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 31 / 38 Le design-pattern Visitor Définition Séparation entre les algorithmes et les objets sur lesquels ils opèrent grâce à un mécanisme de double dispatch. Étapes de mise en place 1) Déclarer une interface Visitor qui offre un ensemble de méthodes visit, chacune paramétrée par un type d’élément à visiter 2) Déclarer une méthode abstraite accept(Visitor v) dans le type général des éléments 3) Dans chaque sous-classe d’élément, implanter accept en faisant juste v.visit(this) 4) Implanter un visiteur concret en définissant toutes les méthodes visit © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 32 / 38 Le design-pattern Visitor : schéma UML Visitor Element + visit(e : ElementA) + accept(v : Visitor) + visit(e : ElementB) ConcreteVisitorX ElementA + visit(e : ElementA) + funcA() + visit(e : ElementB) + accept(v : Visitor) ConcreteVisitorY ElementB + visit(e : ElementA) + funcB() + visit(e : ElementB) + accept(v : Visitor) v.visit(this) element.accept(new ConcreteVisitor()) Client © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 33 / 38 Le design-pattern Visitor : le double dispatch Premier Deuxième dispatch via :elementA dispatch via :visitor liaison tardive surcharge de méthode visit(A a){...} :elementB visit(B b){...} Client e.accept(visitor) :elementC v.visit(this) visit(C c){...} visit(D d){...} :elementD visit(E e){...} :elementE © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 34 / 38 Le design-pattern Visitor : exemple public abstract class Link { private String name ; public Link ( String name ) { this. name = name ;} public String getName () { return name ;} public abstract void accept ( LinkVisitor v ) ; } public class Directory extends Link { private Collection < Link > children = new ArrayList < >() ; public void addLink ( Link l ) { children. add ( l ) ;} public Collection < Link > children () { return new ArrayList < >( children ) ;} public int nbChildren () { return children. size () ;} public void accept ( LinkVisitor v ) { v. visit ( this ) ;} } public class File extends Link { private byte [] content ; public void accept ( LinkVisitor v ) { v. visit ( this ) ;} } public class Symbolic extends Link { private Link target ; public Link getTarget () { return target ;} public void accept ( LinkVisitor v ) { v. visit ( this ) ;} } © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 35 / 38 Le design-pattern Visitor : exemple L’interface LinkVisitor public interface LinkVisitor { public void visit ( File f ) ; public void visit ( Directory d ) ; public void visit ( Symbolic s ) ; } Un visiteur qui compte les Fichiers simples public class C o u n t e r F i l e V i s i t o r implements LinkVisitor { private int cpt =0; public void visit ( File f ) { cpt ++; } public void visit ( Directory d ) { for ( Link l : d. children () ) { l. accept ( this ) ; } } public void visit ( Symbolic s ) {} } © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 36 / 38 Le design-pattern Visitor vs. Iterator Le DP Iterator Il répond à "Comment faire pour parcourir objet B à partir d’un objet A ? Quand s’arrêter ?" Il ne voit qu’un seul type statique (Iterator) Il ne gère pas le traitement à appliquer lorsque l’on parcours un objet Le DP Visitor Il répond à "Que dois-je faire quand on parcours un objet ?" Le traitement à appliquer se détermine statiquement (surcharge méthode) Il peut faire office d’itérateur si les objets sur lesquels il s’applique sont organisés de manière récursive (ex : arbre.) Il est possible de déléguer le parcours des objets à un itérateur dédié for ( VisitableObject o : objets_a_visiter ) { o. accept ( visiteur ) } © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 37 / 38 Le design-pattern Visitor : synthèse Avantages On peut ajouter de nouvelles fonctionnalité aux objets sans modifier les classes (un visiteur = une nouvelle fonctionnalité) Un visiteur permet de définir le traitement à appliquer un ensemble d’objet, le tout dans une même classe Limites Nécessité tout de même d’ajouter la méthode accept dans toutes les classes d’élément (mais changement très mineur) Nécessité de mettre à jour tous les visiteurs à chaque fois que l’on ajoute/supprime un type d’élément visitable Le visiteur n’a pas accès aux champs privé des objets Principales utilisations analyseur statique de code, traducteur de modèles, compilateurs, parseurs © Jonathan Lejeune (Sorbonne Univ.) Design patterns comportementaux 38 / 38