Command
Transcription
Command
Conception et programmation orientées objet avancées Le design pattern Commande (Command) (L'exemple présenté dans ce cours est tiré du livre Tête la première - Design patterns , E. & E. Freeman, ed. O'Reilly) Motivation D.Bouthinon, dpt informatique, IUT Villetaneuse 2 Enregistrer des requêtes et séparer le demandeur du réalisateur d'une requête Exemple : gérer une télécommande domotique allumer ouvrir D.Bouthinon, dpt informatique, IUT Villetaneuse démarrer éteindre 3 Une mauvaise conception D.Bouthinon, dpt informatique, IUT Villetaneuse 4 La télécommande gère les requêtes et les appareils Telecommande +gererOperations(operation:String) Lampe Ventilateur +marche() +arret() +demarrer() +arreter() +accelerer() +ralentir() +getVitesse() PorteGarage +ouvrir() +fermer() +stop() public class Telecommande { private Lampe l, Ventilateur v, PorteGarage p ; ... public void gererOperations(String operation) { if (operation.equals("allumerLampe") {l.allumer() ;} else if (operation.equals("eteindreVentilateur") {v.eteindreVentilateur() ;} ... } } il faut modifier la classe Telecommande dès qu'on ajoute/retire/modifie un appareil ou une requête D.Bouthinon, dpt informatique, IUT Villetaneuse 5 Une bonne conception D.Bouthinon, dpt informatique, IUT Villetaneuse 6 Chaque requête et l'appareil qui doit la réaliser sont encapsulés dans un objet (commande) public interface Commande { public abstract void executer() ; } public class AllumerLampe implements Commande { private Lampe lampe ; public AllumerLampe(Lampe uneLampe) { this.lampe = uneLampe ; } public void executer() { this.lampe.marche() ; } lalalampe lampeààcommander commanderest estencapsulée encapsulée dans la commande dans la commande l'opération l'opérationexecuter() executer()définit définit lalarequête encapsulée dans la requête encapsulée dans lacommande. commande. lalarequête requêteinvoque invoqueune une(ou (ouplusieurs) plusieurs) opération(s) de la lampe opération(s) de la lampe } D.Bouthinon, dpt informatique, IUT Villetaneuse 7 La télécommande enregistre et active les commandes public class Telecommande { private Commande lampeOn ; private Commande lampeOff ; public Telecommande() {} pour pourmémoriser mémoriserles lescommandes commandes pour pourenregistrer enregistrerles lescommandes commandes public void setLampeOn(Commande c) {this.lampeOn = c ;} public void setLampeOff(Commande c) {this.lampeOff = c ;} public void activeLampeOn() {this.lampeOn.executer();} pour pouractiver activerles lescommandes commandes (qui ont toutes le même appel (qui ont toutes le même appelààexecuter()) executer()) public void activeLampeOff() {this.lampeOff.executer() ;} } Il est facile d'ajouter/retirer des commandes à la Telecommande parce qu'elle ne gère ni la lampe ni les opérations qu'elle réalise, le tout étant encapsulé dans les commandes. D.Bouthinon, dpt informatique, IUT Villetaneuse 8 L'utilisateur (client) de la télécommande crée les commandes et les appareils public class Client { public static void main(String[] args) { Telecommande t = new TeleCommande() ; Lampe la = new Lampe() ; Commande Commande al = new AllumerLampe(la) ; el = new EteindreLampe(la) ; t.setLampeOn(al) ; t.setLampeOff(el) ; t.activeLampeOn() ; t.activeLampeOff() ; création créationde delalaTelecommande, Telecommande, création créationde delalaLampe Lampeààcommander... commander... création créationdes descommandes commandes (contenant la Lampe (contenant la Lampeààcommander) commander) enregistrement enregistrementdes descommandes commandes dans Telecommande dans Telecommande } } D.Bouthinon, dpt informatique, IUT Villetaneuse activation activationdes descommandes commandesde delalaTelecommande Telecommande 9 Conception globale Client Telecommande +setLampeOn(c:Command) +setLampeOff(c:Command) +activeLampeOn() +activeLampeOff() EteindreLampe +executer() 1..* Lampe <<interface>> +marche() +arret() Commande +executer() AllumerLampe +executer() Le Client s'adresse à la Telecommande qui s'adresse à une Commande qui s'adresse à la Lampe La Telecommande ne connaît que les Commande(s) : on a dissocié le demandeur d'une requête (la Telecommande) du réalisateur de la requête (la Lampe). D.Bouthinon, dpt informatique, IUT Villetaneuse 10 Dynamique :Telecommande :Client 1. new AllumerLampe(Lampe) :Lampe c: AllumerLampe enregistre la commande 2. setLampeOn(c) 3. activeLampeOn() 3.1 execute() active la commande enregistrée 3.1.1 marche() Le Client 1. créé la commande, 2. l'enregistre dans la Telecommande, 3. demande à la Télécommande de l'activer : 3.1 la Telecommande s'adresse à la commande 3.1.1 qui s'adresse à la Lampe D.Bouthinon, dpt informatique, IUT Villetaneuse 11 Ajout d'une commande pour le Ventilateur public interface Commande { public abstract void executer() ; } public class ActionnerVentilateur implements Commande { Le LeVentilateur Ventilateurààcommander commanderest estencapsulé encapsulé private Ventilateur ventilateur ; dans la Commande dans la Commande } public ActionnerVentilateur(Ventilateur unVentilateur) { this.ventilateur = unVentilateur ; l'opération l'opérationexecuter() executer()définit définit } lalarequête encapsulée dans la Commande. requête encapsulée dans la Commande. C'est la même C'est la mêmesignature signaturepour pourtoutes toutesles lescommandes commandes public void executer() { this.ventilateur.demarrer() ; while(this.ventilateur.getVitesse() < 50) this.ventilateur.accelerer() ; La Larequête requêtedialogue dialogueavec avecleleVentilateur Ventilateur } pour obtenir l'état souhaité pour obtenir l'état souhaité D.Bouthinon, dpt informatique, IUT Villetaneuse 12 La télécommande mémorise les commandes de la Lampe et du Ventilateur public class Telecommande { private Commande lampeOn, ventilateurOn ; private Commande lampeOff, ventilateurOff ; ... public void setVentilateurOn(Commande c) {this.ventilateurOn = c ;} ajout ajoutdes desCommande(s) Commande(s)du duVentilateur Ventilateur pour pouraffecter affecterles lesnouvelles nouvellescommandes commandes public void setVentilateurOff(Commande c) {this.ventilateurOff = c ;} public void activeVentilateurOn() {this.ventilateurOn.executer() ;} public void activeVentilateurOff() {this.ventilateurOff.executer() ;} Les Lesméthodes méthodesactivant activantles lescommandes commandesde delalalampe lampe etetdu ventilateur font toutes appel à executer() du ventilateur font toutes appel à executer() public void activeLampeOn() {this.lampeOn.executer();} public void activeLampeOff() {this.lampeOff.executer() ;} } D.Bouthinon, dpt informatique, IUT Villetaneuse 13 Le Client gère la Lampe et le Ventilateur public class Client { public static void main(String[] args) { Telecommande t = new TeleCommande() ; Lampe Ventilateur Commande Commande Commande Commande al el av ev la v = new Lampe() ; = new Ventilateur() ; = = = = new new new new création créationdes desappareils appareilsààcommander commander AllumerLampe(la) ; EteindreLampe(la) ; ActionnerVentilateur(v) ; EteindreVentilateur(v) ; t.setLampeOn(al) ; t.setLampeOff(el) ; t.setVentilateurOn(av) ; t.setVentilateurOff(ev) ; t.appuiLampeOn() ; t.appuiLampeOff() ; t.appuiVentilateurOn() ; t.appuiVentilateurOff() ; création créationdes descommandes commandes affectation affectationdes descommandes commandesàà lalaTelecommande Telecommande activation activationdes descommandes commandes de la Telecommande de la Telecommande } } D.Bouthinon, dpt informatique, IUT Villetaneuse 14 Conception globale Client Telecommande +setLampeOn(c:Command) +setLampeOff(c:Command) +setVentilateurOn(c:Command) +setVentilateurOff(c:Command) +activeLampeOn() +activeLampeOff() +activeVentaliteurOn() +activeVentilateurOff() EteindreLampe ActionnerVentilateur +executer() +executer() 1..* Lampe Ventilateur +arreter() +demarrer() +accelerer() +ralentir() +getVitesse() <<interface>> +marche() +arret() Commande +executer() AllumerLampe EteindreVentilateur +executer() +executer() La Telecommande ne connaît que les Commande(s). D.Bouthinon, dpt informatique, IUT Villetaneuse 15 Inversibilité d'une commande On souhaite pouvoir annuler toute commande exécutée Une commande est annulée lorsqu'on retourne dans l'état précédant son exécution allumée éteindre éteinte allumer allumée éteinte annuler D.Bouthinon, dpt informatique, IUT Villetaneuse 17 On ajoute une opération abstraite annuler() dans l'interface Commande public interface Commande { public abstract void executer() ; public abstract void annuler() ; } D.Bouthinon, dpt informatique, IUT Villetaneuse 18 On implémente annuler() dans chaque Commande public class AllumerLampe implements Commande { private Lampe lampe ; public AllumerLampe(Lampe uneLampe) {this.lampe = uneLampe ;} public void executer(){this.lampe.marche() ;} public void annuler(){this.lampe.arrete() ;} lampe.arrete() lampe.arrete()restaure restaurel'état l'état de la lampe précédent l'appel de la lampe précédent l'appelàà lampe.marche() lampe.marche(): : } public class EteindreLampe implements Commande { private Lampe lampe ; public EteindreLampe(Lampe uneLampe) {this.lampe = uneLampe ;} public void executer(){this.lampe.arrete() ;} public void annuler(){this.lampe.marche() ;} lampe.marche() lampe.marche()restaure restaurel'état l'état de la lampe précédent l'appel de la lampe précédent l'appel àà lampe.arrete() lampe.arrete(): : } D.Bouthinon, dpt informatique, IUT Villetaneuse 19 On ajoute une opération annulation à la Telecommande public class Telecommande { private Commande lampeOn, lampeOff private Commande commandeActivee ; ; pour pourmémoriser mémoriserlaladernière dernièrecommande commandeexécutée exécutée public Telecommande() {} public void setLampeOn(Commande c) {this.lampeOn = c ;} public void setLampeOff(Commande c) {this.lampeOff = c ;} public void activeLampeOn() { On this.lampeOn.executer(); Onmémorise mémoriselalacommande commandequi quivient vientd'être d'êtreexécutée exécutée this.commandeActivee = this.lampeOn ; } public void activeLampeOff() { this.lampeOff.executer() ; this.commandeActivee = this.lampeOff ; } public void annulation() {this.commandeActivee.annuler() ;} SiSil'annulation l'annulationest estdemandée, demandée,laladernière dernière commande activée exécute son opération commande activée exécute son opération annuler() annuler() } D.Bouthinon, dpt informatique, IUT Villetaneuse 20 Le Client peut annuler les commandes public class Client { public static void main(String[] args) { Telecommande t = new TeleCommande() ; Lampe la = new Lampe() ; Commande Commande al = new AllumerLampe(la) ; el = new EteindreLampe(la) ; t.setLampeOn(al) ; t.setLampeOff(el) ; t.activeLampeOn() ; t.activeLampeOff() ; t.annulation() ; } } annulation annulationde delaladernière dernièreCommande Commande: : lalaLampe repasse à l'état Lampe repasse à l'état"allumée" "allumée" D.Bouthinon, dpt informatique, IUT Villetaneuse 21 Annuler une Commande du ventilateur L'annulation d'une commande du ventilateur (ex : accélérer) retourne à un état différent (lent ou moyen) selon l'état de départ (moyen ou rapide) lent accélérer annuler moyen accélérer annuler Il faut mémoriser le dernier état du ventilateur rapide D.Bouthinon, dpt informatique, IUT Villetaneuse 22 Diagrammes états-transitions du Ventilateur 4 états et 4 actions ralentir() accelerer() arrêté demarrer() arreter() arreter() arreter() ralentir() rotation lente arreter() ralentir() demarrer() accelerer() rotation rapide ralentir() accelerer() demarrer() accelerer() rotation moyenne demarrer() D.Bouthinon, dpt informatique, IUT Villetaneuse 23 Représentation et gestion des états public class Ventilateur { public final static int ARRET = 0, LENT = 25, MOYEN = 45, RAPIDE = 100 ; private int etat = ARRET, etatPrecedent = ARRET ; ... avant avantchaque chaqueopération opérationon on public void accelerer(){ mémorise mémorisel'état l'étatcourant courantcomme commeétat étatantérieur antérieur this.etatPrecedent = this.etat ; if (this.etat = LENT) this.etat = MOYEN ; if (this.etat = MOYEN) this.etat = RAPIDE ; } public void ralentir(){ this.etatPrecedent = this.etat ; if (this.etat = RAPIDE)this.etat = MOYEN ; if (this.etat = MOYEN) this.etat = LENT ; } puis puison onprocède procèdeau auchangement changementd'état d'état (si on ne fait rien on reste dans le même (si on ne fait rien on reste dans le mêmeétat) état) public void demarrer(){ this.etatPrecedent = this.etat ; if (this.etat = ARRET) this.etat = LENT ;} public void arreter(){ this.etatPrecedent = this.etat ; this.etat = ARRET ;} public void annuler() {this.etat = this.etatPrecedent ;} annuler() annuler()ramène ramèneàà l'état l'étatprécédent précédent Une bien meilleure conception consiste à utiliser le design pattern Etat (cours à venir) D.Bouthinon, dpt informatique, IUT Villetaneuse 24 Commande du Ventilateur avec annuler() public class AccelererVentilateur implements Commande { private Ventilateur ventilateur ; public AccelererVentilateur(Ventilateur unVentilateur) { this.ventilateur = unVentilateur ; } public void executer() { this.ventilateur.acceler() ; } public void annuler() { this.ventilateur.annuler() ; } } D.Bouthinon, dpt informatique, IUT Villetaneuse permet permetau auventilateur ventilateurde derevenir revenirdans dansl'état l'étatprécédent précédent laladernière Commande effectuée. dernière Commande effectuée. délègue délèguelalagestion gestionde del'annulation l'annulationau auventilateur ventilateur 25 La télécommande est munie d'une fonction d'annulation public class Telecommande { private Commande lampeOn, accelereVentilateur ; private Commande lampeOff, ralentitVentilateur ; private Commande derniereCommande ; ... pour pourmémoriser mémoriserlaladernière dernièrecommande commandeactivée activée par parlalatélécommande télécommandeafin afinde depouvoir pouvoirl'annuler. l'annuler. public void activeLampeOn() {this.lampeOn.executer(); this.derniereCommande = this.lampeOn ;} public void activeLampeOff() {this.lampeOff.executer(); this.derniereCommande = this.lampeOff ;} on onmémorise mémorisechaque chaquecommande commandeactivée activée public void activeAccelereVentilateur() {this.accelereVentilateur.executer() ; this.derniereCommande = this.accelereVentilateur ;}} public void activeRalentitVentilateur() {this.ralentitVentilateur.executer() ; this.derniereCommande = this.ralentirVentilateur ;}} public void annulation() {this.commandeActivee.annuler()} } D.Bouthinon, dpt informatique, IUT Villetaneuse annulation() annulation()annule annulelaladernière dernièrecommande commandeactivée activée (que cette commande concerne la lampe (que cette commande concerne la lampe ou ouleleventilateur) ventilateur) 26 Le Client gère les commandes et les annulations public class Client { public static void main(String[] args) { Telecommande t = new TeleCommande() ; Lampe la = new Lampe() ; Ventilateur v = new Ventilateur() ; Commande al = new AllumerLampe(la) ; Commande el = new EteindreLampe(la) ; Commande av = new AccelererVentilateur(v) ; Commande ev = new RalentirVentilateur(v) ; Commande dv = new DemarrerVentilateur(v) ; t.setLampeOn(al) ; t.setLampeOff(el) ; t.setVentilateurOn(av) ; t.setVentilateurOff(ev) ; t.appuiLampeOn() ; t.appuiLampeOff() ; t.annulation() ; t.demarrerVentilateur() ; Le Leventilateur ventilateurdémarre démarreen enmode modelent lent t.activeAccelereVentilateur() ; puis puispasse passeenenmode modemoyen moyen t.activeRalentitVentilateur() ; t.annulation() ; puis puislent lentde denouveau nouveau } } D.Bouthinon, dpt informatique, IUT Villetaneuse On Onannule annule: :ililseseremet remeten enmode modemoyen moyen 27 Créer des macros-commandes Créer une Commande exécutant plusieurs Commandes public class MacroCommande implements Commande { private Commandes[] commandes ; pour pouraccueillir accueillirles lescommandes commandes public MacroCommande(Commande[] desCommandes) { this.commandes = desCommandes ;} leleconstructeur constructeurfournit fournitles lescommandes commandes public void executer() { for (int i = 0 ; i < this.commandes.len ; i++) this.commandes[i].executer() ; } exécute exécutetoutes toutesles lescommandes commandes public void annuler() { for (int i = 0 ; i < this.commandes.len ; i++) this.commandes[i].annuler() ; } } D.Bouthinon, dpt informatique, IUT Villetaneuse annule annuletoutes toutesles lescommandes commandes 29 Enregistrement la macro commande La macro-commande est traité comme une Commande public class Telecommande { ... private Commande macroActionneTout ; pour pourmémoriser mémoriserlalamacro-commande macro-commande private derniereCommande ; public void setMacroActionneTout(Commande c) { this.macroActionneTout = c ; } pour pourenregistrer enregistrerlalamacro-commande macro-commande public void activeMacroActionneTout() pour pouractiver activerlalamacro-commande macro-commande { this.macroActionneTout.executer() ; this.derniereCommande = this.actionneTout ;} } ... } D.Bouthinon, dpt informatique, IUT Villetaneuse 30 Le Client construit la macro-commande public class Client { public static void main(String[] args) { Telecommande t = new TeleCommande() ; Lampe Ventilateur la v = new Lampe() ; = new Ventilateur() ; Commande Commande al dv = new AllumerLampe(la) ; = new DemarrerVentilateur(v) ; Commande[] desCommandes = {a1, dv} ; Commande activeTout t.activeMacroAllumeTout() ; } D.Bouthinon, dpt informatique, IUT Villetaneuse on onles lesplace placedans dansun untableau... tableau... = new MacroCommande(desCommandes) ; t.setMacroAllumeTout(activeTout) ; } On Oncréé crééles lescommandes commandes on onactive activelalamacro-commande macro-commande ...qu'on ...qu'onplace placedans dansune une macroCommande macroCommande on onenregistre enregistrelalamacro macrocommande commande dans la télécommande dans la télécommande 31 Le design pattern Commande Définition Le design pattern Commande encapsule une requête comme un objet, autorisant le paramétrage des clients par différentes requêtes, files d'attente et récapitulatif de requêtes, et permet la réversibilité des opérations. Design Patterns : Elements of Reusable Object-Oriented Software (Design Patterns : catalogue des modèles de conception réutilisables) E. Gamma, R. Helm, R. Johnson et J. Vlissides D.Bouthinon, dpt informatique, IUT Villetaneuse 33 Structure du design pattern Commande contient et active les commandes Invocateur +setCommand(c:Command) +activeCommand() Client <<interface>> 1..* Commande +executer() +annuler() CommandeConcrete +executer() +annuler() a) créé les commandes et les affecte à l'Invocateur b) créé le Receveur Receveur executer() invoque l'opération action() du Receveur +action() (dans l'exemple précédemment présenté l'invocateur est la télécommande, les receveurs sont les lampes et le ventilateur) D.Bouthinon, dpt informatique, IUT Villetaneuse 34 Dynamique du design pattern Commande :Invocateur :Client 1. new CommandeConcrete(Receveur) :Receveur c:CommandeConcrete 2. enregistrer(c) 3. activeCommande() 3.1 executer() 3.1.1 action() Le Client 1. créé la commande, 2. l'enregistre dans l'Invocateur, 3. demande à l'Invocateur de l'activer : l'Invocateur 3.1 s'adresse à la commande 3.1.1qui s'adresse au Receveur D.Bouthinon, dpt informatique, IUT Villetaneuse 35 Principes de conception rencontrés Principes généraux mises en œuvre Couplage faible Dans le design pattern Commande l'invocateur (télécommande) n'est couplé qu'aux commandes. L'invocateur est découplé des receveurs (lampes et ventilateur) et de leurs opérations qui sont encapsulés dans les commandes. Séparer ce qui change du reste En encapsulant les receveurs et les requêtes dans les commandes on a, dans l'Invocateur, "séparé ce qui change du reste". Conclusion : L'invocateur est grandement simplifié, on peut facilement le modifier sans effet de bord sur les receveurs. D.Bouthinon, dpt informatique, IUT Villetaneuse 37