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