giovedì 23 febbraio 2012

Il pattern command, un esempio concreto

Oggi vorrei descrivere uno dei pattern base del catalogo GOF (Gang of four), il pattern command.

Mi sono trovato a dover realizzare da zero un tool a riga di comando per intefacciarmi via JMX ad un server applicativo e lanciare quindi dei comandi sul server stesso.
Il problema fondamentale era quindi quello di non sapere a priori quanti comandi mi sarebbero serviti nel corso del tempo, quindi occorreva una soluzione un pò flessibile per poter manutenere agevolmente l'applicazione.

Il pattern command faceva al mio caso dato che consente di astrarre il concetto di esecuzione di un comando dal comando stesso e quindi dalle operazioni specifiche che vengono effettuate.

Prima di addentrarci nel merito della vicenda facciamo un esempio per chiarire il tema:
Potrei eseguire a riga di comando il mio MyJMXTool in uno di questi modi:

MyJMXTool --list

MyJMXTool --delete oldResource

MyJMXTool --add newResource

MyJMXTool --delete oldResource --add newResource --list

Da questo esempio si evince quindi che dovrò partire con implementare almeno 3 comandi: list, add e delete.

A questo punto andiamo a vedere come ho implementato il pattern command:

  • Interfaccia Command con metodo execute() che astrae l'esecuzione del comando.
  • Classe ConcreteCommand che contiene alcune informazioni comuni legate al generico comando:
    • il nome,
    • la stringa per l'help in linea,
    • la sintassi attesa
    • un riferimento alla mia classe MyJMXTool in modo da poter accedere a tutto quello di cui ho bisogno.
  • Classi ListCommand, AddCommand e DeleteCommad che estendono ConcreteCommand e implementano Command.


In teoria dentro il metodo execute dei comandi concreti avrei dovuto implementare gli algoritmi legati ai comandi da eseguire, ma dato che il mio tool è un tantino più complicato, i metodi reali vengono implementati dentro la classe principale MyJMXTool e ciascun metodo execute dei miei comandi invoca di volta in volta tool.list(), tool.add() e tool.delete() dove ovviamente “tool” è la variabile di classe di tipo MyJMXTool ereditata dal ConcreteCommand.

A contorno di tutto ciò ho realizzato:
  • una mappa contenente la lista dei comandi accettati
  • un vettore contenente i comandi passati in input dalla riga di comando


Una volta che ho validato quindi la mia chiamata da riga di comando controllando che tutti i comandi e le opzioni siano consentite, non mi resta che lanciare un metodo di esecuzione simile a questo:



public int executeCommands() {
int code = AMQ5CommandList.NORMAL;

ListIterator li = commands.listIterator();
while(li.hasNext()) {
Command cmd = (Command)li.next();
code = cmd.execute();
}
return code;
}



Come si può vedere, l’esecuzione dei singoli comandi è slegata dall’implementazione e sfrutta la definizione dell’interfaccia.
Dovendo aggiungere nuovi comandi sarà sufficiente scrivere la classe concreta per lo specifico comando e implementare il metodo all’interno della classe MyJMXTool.

Design e Refactoring >> Si poteva fare meglio? Penso proprio di si.
  • Si poteva creare una classe CommandExecuter che conteneva il metodo di esecuzione appena esposto e l’istanza del tool.
  • Il metodo execute poteva prendere in ingresso il tool stesso, in questo modo avrei potuto legare l’algoritmo specifico al metodo execute del comando concreto e sarebbe bastato aggiungere soltanto una derivazione della classe ConcreteCommand per aggiungere nuovi comandi invece che modificare 2 classi differenti.


Spero di essere stato chiaro e di aver reso l’idea di una reale utilizzazione di questo design pattern.

Nessun commento:

Posta un commento