Command pattern: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
Nessun oggetto della modifica
Perseo (discussione | contributi)
aggiunto esempio e considerazioni
Riga 8:
 
[[Immagine:CommandPattern.png|center|Struttura del Design Pattern '''Command''']]
 
==Esempio==
 
In un modulo è definito il seguente oggetto.
 
<pre>
class RoomHandler:
...
</pre>
 
Esso è destinato ad occuparsi della gestione delle camere di una casa e, attualmente, presiede anche allo svolgimento di alcuni lavori da svolgere.
 
Sono infatti definiti, per ereditarietà, alcune sottoclassi che implementano lavori diversi da eseguire sulle pareti delle camere.
 
<pre>
class Painter(RoomHandler)
...
def actionWork(self, arguments):
""" paint some walls """
...
 
class PaperGluer(RoomHandler)
...
def actionWork(self, arguments):
""" glue wallpaper to some walls """
...
 
class ShelfMounter(RoomHandler)
...
def actionWork(self, arguments):
""" mount shelves to some walls """
</pre>
 
Questo approccio ha ha alcune conseguenze negative.
 
1. Se esistono ''n'' possibili lavori (azioni) ci si ritrova costretti ad avere molte sottoclassi. Non solo una per azione da implementare ma anche quelle che servono per le possibili combinazione di azioni che voglio ottenere (ad esempio dipingere camera e montarci degli scaffali). Se invece le azioni sono ''Command'' indipendenti dal RoomHandler, posso istanziarli su quest'ultimo senza dover subclassare.
 
2. Se ''RoomHandler'' contiene sia il codice di gestione delle camere che il codice che svolge i lavori: in sostanza ha troppe responsabilità e se voglio estendere un lavoro devo complicare di conseguanza anche ''RoomHandler''.
 
3. Testare oggetti così complessi, con molte responsabilità, diventa faticoso e complesso. Se le azioni sono incapsulate in ''Command'' testate separatamente riesco a semplificare anche i test (eventualmente ricorrendo all'uso di [[Mock Objects|Mock]].
 
Volendo quindi rifattorizzare il codice precedente:
 
<pre>
class Command:
 
def execute(self):
raise NotImplementedError
 
class PaintWallCommand(Command)
 
def execute(self, wall):
""" paint a wall """
 
class GlueWallPaperCommand(Command)
 
def execute(self, wall):
""" glue wall paper to a wall """
 
class MountShelfCommand(Command)
 
def execute(self, wall):
""" mount shelf to a wall """
</pre>
 
Al momento della creazione di un ''RoomHandler'' è possibile selezionare i possibili lavori da svolgere:
 
<pre>
def createRoomHandler(self):
handler = RoomHandler()
handler.addWork(PaintWallCommand())
handler.addWork(MountShelfCommand())
 
class RoomHandler(self)
...
def actionWork(self):
for work in self.getWorks():
work.execute(self.getSelectedWall())
</pre>
 
 
==Considerazioni==
 
1. Il ricevente dell'operazione (nel nostro caso la parete dove lavorare) non è deciso al momento della creazione dei lavori da svolgere, ma a tempo di esecuzione.
 
2. E' possibile incapsulare un'azione in modo che essa sia atomica. In questo modo si implementerebbe un meccanismo di transazionalità in cui un'insieme di operazioni è svolto in toto o per nulla.
 
3. I ''Command'', conoscendo le operazioni che devono svolgere, possono implementare anche un ''unexecute'' o ''undo''. Prima di eseguire, il Command, ricorda lo stato precedente alla sua esecuzione in modo da annullare eventualmente la sua operazione.
 
4. E' possibile rendere asincrona la scelta dei comandi rispetto alla loro esecuzione. Un certo numero di command, selezionati da un client, possono essere ''consumati'' da un'altro oggetto che li riceve in un tempo diverso dalla loro selezione.
 
 
==Link==
* http://hatena.dyndns.org/~jkondo/DesignPattern/Command/
* http://www.martinfowler.com/eaaCatalog/unitOfWork.html