TikeSwing è un framework open source che permette di implementare il pattern MVC nelle nostre applicazioni desktop che fanno uso della libreria grafica Swing. Il framework semplifica e velocizza lo sviluppo permettendo una “copia” diretta dei dati dal model alla view e viceversa senza il classico intervento del programmatore. Inoltre fornisce una serie di strumenti utili per gestire la comunicazione dei vari moduli di una applicazione e l‘interazione con l‘utente.
Introduzione
Quando sviluppiamo un‘applicazione desktop con interfaccia grafica, una delle cose più noiose è la parte in cui dobbiamo “copiare” i nostri dati, provenienti ad esempio da un db, in una GUI per visualizzarle all‘utente. Cosa altrettanto noiosa è l‘operazione inversa, ovvero “recuperare” i dati dalla GUI per effettuare operazioni come salvataggi, invio ecc. Pensiamo poi alla difficoltà che si presenta quando è necessaria una modifica alla struttura, soprattutto se il tutto è incluso in un‘unica classe! Inoltre si rischia di creare non poca confusione nel codice generando classi gigantesche e ripetendo sempre le solite routine. Per ovviare a queste difficoltà e snellire il lavoro si può ricorrere all‘uso di un pattern, il Model-View-Controller, conosciuto come MVC e molto noto soprattutto in ambito WEB. Questo pattern permette di dividere lo strato di definizione dei dati dall‘effettiva presentazione di questi a video e dalle operazioni che vengono effettuate su di essi. In questo articolo tratteremo questo pattern e faremo una introduzione ad un framework open source, TikeSwing[1], grazie al quale è possibile implementare in ambito desktop, il tutto pure-Swing, il pattern sopra citato.
Il pattern MVC
Model-View-Controller (a volte tradotto in italiano Modello-Vista-Controllore) è il nome di un design-pattern fondamentale nello sviluppo di interfacce grafiche[2]. Originariamente fu impiegato dal linguaggio Smalltalk. Come tutti i pattern venne ideato per risolvere uno specifico problema, ovvero la separazione logica tra chi presenta i dati all‘utente (view), chi contiene i dati (model) e chi usa le operazioni offerte da queste due entità (controller). In Figura 1 è rappresentato il diagramma UML di un classico MVC.
Figura 1 – Il pattern MVC
Vediamo ora il singolo ruolo dei tre partecipanti:
Il model è il cuore del pattern in quanto definisce i dati e le operazioni che possono essere eseguite su essi, fornendo delle funzioni per l‘accesso e l‘aggiornamento degli stessi. Può avere la responsabilità di notificare eventuali aggiornamenti verificatisi nella view (spesso usando un altro pattern noto col nome di “Observer” [3]) e permettere di presentare agli utenti dati sempre aggiornati.
La view è la classica interfaccia grafica che presenta i dati e con cui l‘utente interagisce (GUI).
Il controller implementa la logica di controllo dell‘applicazione comportandosi adeguatamente in base agli eventi che l‘utente comunica attraverso la view.
TikeSwing e l‘MVC
TikeSwing, come abbiamo detto, è un framework che ci permette di realizzare agevolmente il pattern MVC dentro le nostre applicazioni usando le librerie Swing. Infatti tutti i componenti “view” che fanno parte di TikeSwing non sono altro che estensioni dei classici componenti Swing con l‘aggiunta degli strumenti per realizzare il nostro MVC. Restano quindi invariate tutte le conoscenze sull‘uso di Swing salvo qualche eccezione sugli eventi (ActionListener ed altri che vedremo più avanti). Passiamo ora a vedere come implementare singolarmente tutti i partecipanti al pattern usando il framework.
Model
Per implementare il nostro model non dobbiamo fare altro che definire una classe che estende YModel, inserire come proprietà i dati che visualizzeremo nella view e i relativi metodi di accesso ai dati (i classici getter e setter definiti nella specifica Javabean). Tutto quello che serve per notificare il controllore dei cambiamenti è già implementato nella classe YModel (tramite il pattern Observer). Ecco l‘esempio di un semplice model.
public class MyModel extends YModel{
private String nome="";
private String cognome="";
public void MyModel(){
super();
}
public void getNome(){
return nome;
}
public void setNome(String nome){
this.nome=nome;
}
public void getCognome(){
return cognome;
}
public void setCognome(String cognome){
this.cognome=cognome;
}
}
Possiamo anche inserire dati con strutture complesse come array, collection o bean creati appositamente per definire una entità nella nostra applicazione (ad esempio “Automobile”). Tutto quello che dobbiamo fare è aggiungere i soliti metodi getter e setter per la nostra variabile come mostrato nel listato appena visto, vedremo successivamente come trattare i singoli casi.
Tutto qui…
View
Passiamo ora a vedere come implementare il nostro strato di presentazione dei dati, ovvero la nostra view. Una view è una finestra, un dialogo o un pannello che contiene i componenti che visualizzano i dati memorizzati nel modello (campi di testo, liste, tabelle ecc) e i componenti che permettono all‘utente di interagire e richiedere operazioni (bottoni ecc). TikeSwing offre quasi tutti i componenti come estensione di Swing e nella maggior parte dei casi la differenza sta nell‘usare il prefisso “Y” anzichà© “J”, quindi JTextField sarà YTextField, JPanel diverrà YPanel e così via. Detto questo supponiamo che la nostra view sia una finestra useremo il componente YFrame. A questo punto creiamo la finestra come abbiamo sempre fatto (a mano o con un editor visuale) facendo attenzione a usare il relativo componente “Y” al posto di quello standard Swing. In particolare facciamo attenzione al fatto che tutti i componenti che fanno parte della view abbiano un getter pubblico (questo per questione di framework visto che TikeSwing usa un meccanismo di reflection per l‘accesso ai componenti). È anche possibile specificare una lista di componenti, ma questa va aggiornata ad ogni modifica effettuata nella view, quindi è consigliabile lasciare al framework l‘onere di “scovare” i componenti da collegare con il model. Ultimata la view dobbiamo effettuare la mappatura con i rispettivi campi del model in modo che ad ogni componente corrisponda la relativa proprietà contenuta nel model, cosa che vedremo più avanti nell‘articolo.
Controller
Il controller viene implementato per mezzo dell‘estensione della classe YController che implementa tutto il necessario per la creazione del componente controller in un MVC standard. Infine per effettuare il collegamento tra tutti e tre i partecipanti (model, view e controller), facciamo riferimento ad un metodo statico contenuto in una classe di utilità che ha il solo compito di “linkare” i componenti tra di loro:
YUIToolkit.setUpMVC(YModel model, YComponent view, YController controller);
Ed ecco pronto il nostro MVC!
In questo secondo listato, riportiamo un piccolo esempio di controller:
public class MyController extends YController{
//la view
private MyView view = new MyView();
//il model
private MyModel model=new MyModel();
//un costruttore standard
public MyController(){
super();
YUIToolkit.setUpMVC(model,view, this);
//serve a "copiare" i dati dal model nella view
copyToView("");
}
}
A questo punto inseriremo nel controller tutti i metodi di cui abbiamo bisogno per realizzare la nostra applicazione.
Riepilogo
Per implementare il nostro MVC dobbiamo quindi semplicemente estendere quello che il framework ci propone ed è fatto. In Figura 2 vediamo un riepilogo grafico di quello che abbiamo fatto.
Figura 2 – Diagramma UML di una implementazione standard.
Per essere esaustivo il diagramma avrebbe dovuto includere anche il pattern Observer [3], questo però non è stato inserito per evitare confusione. Quest‘ultimo, come abbiamo detto, è quello che permette ai 3 componenti di dialogare tra di loro, ma non è necessario capirne il funzionamento in questo punto dell‘articolo, sarà trattato in maniera esaustiva nel prossimo.
Mappare i dati
Abbiamo visto che dobbiamo mappare i dati per permettere la visualizzazione di quanto memorizzato nel model all‘interno della view, ma come fare? TikeSwing usa a sua volta una libreria open source facente parte di Jakarta, commons BeanUtils[4]. Quest‘ultima permette di accedere ai dati di un bean tramite un meccanismo di reflection [5], ovvero invocando metodi a runtime su di un oggetto conoscendone il nome. Per questo motivo abbiamo bisogno di specificare al framework che il campo chiamato nome dentro il modello va copiato nella view nel componente textNome e viceversa. Per effettuare questo il framework mette a disposizione un proprietà (YProperty) in cui è possibile specificare alcune proprietà del componente (scusate il gioco di parole…). Nel nostro caso abbiamo bisogno di specificare l‘ MVC_NAME ovvero il nome MVC del componente. Per fare questo predisponiamo all‘interno della view un metodo che fa proprio questo come illustrato nel terzo listato.
//metodo per settare tutti gli MVC_NAME
private void setUpMVCNames(){
getYProperty().put(MVC_NAME,"view");
getTextNome().getYProperty().put(MVC_NAME,"nome");
getTextCognome().getYProperty().put(MVC_NAME,"cognome");
}
Il codice appena visto indicherà al framework che il componente textNome è collegato alla proprietà nome nel model e viceversa. Quando invocheremo il metodo copyToView(“nome”) il framework sarà a conoscenza di quale dato copiare e in quale componente della view. Se invece invocheremo copyToView(“”), passando una stringa vuota, il framework andrà a cercare tutti i componenti nella view che abbiano un MVC_NAME e copierà tutti i dati all‘interno della view. Per completezza, esiste un metodo “copyToModel(“”)” che fa l‘esatto contrario del precedente, ovvero copia i dati dalla view nel model. Tuttavia non è necessario invocarlo manualmente dato che appena i dati inseriti da un utente in un componente diventano “definitivi”, esempio un campo di testo perde il fuoco, il dato viene aggiornato automaticamente nel model. Ecco un breve elenco del mapping che è possibile fare:
Semplice: un componente è collegato direttamente al modello
MVC_NAME="nomeNelModello"
Annidato : un componente è collegato a un campo dentro un bean (esempio persona.nome)
MVC_NAME="nomeNelModello.proprietaDaMappare"
Indicizzato: un componente è collegato a un campo dentro un array contenuto nel model
MVC_NAME="nomeArrayNelModello[1]"
Mappato: un componente è collegato a un campo dentro una Map contenuta nel model
MVC_NAME="nomeMapNelModello("chiave")"
Per una descrizione più approfondita si fa riferimento alla documentazione di jakarta commons BeanUtils. Una cosa sulla quale è bene porre l‘attenzione è che il metodo copyToView(“field”) cercherà non solo la proprietà con quel nome, ma in realtà “field” è il prefisso del campo da copiare (che può ovviamente coincidere con quel campo). Questo può sembrare una pecca, tuttavia ci viene in aiuto in quei casi il cui abbiamo mappato tutte le proprietà di un oggetto (es Automobile), ma nella view abbiamo anche altri oggetti (es Proprietario). Nel caso in cui volessimo aggiornare solo i dati relativi ad un oggetto (es solo l‘oggetto Automobile) e nessun altro dovremmo richiamare copyToView() per tutte le proprieà ( “auto.marca”, “auto.modello” ecc). Possiamo invece in questo caso invocare copyToView(“auto”) e il framework aggiornerà a video tutte le proprietà dell‘oggetto auto. Per lo stesso motivo possiamo aggiornare un‘intera view semplicemente invocando copyToView(“”) senza sapere minimamente quali, e quanti, campi ci sono dentro il model.
Eventi
Fino ad ora abbiamo visto come trattare i dati, in questo paragrafo andremo a vedere come trattare gli eventi come la pressione di un bottone o la chiusura di una finestra. Nel listato uno compare una riga di codice che setta come MVC_NAME della view la stringa “view”, ma non abbiamo alcuna proprietà nel modello da mappare con la view. La proprietà MVC_NAME serve infatti a definire un “nome” per quel componente, non necessariamente a mappare dei dati. In particolare se si prova a chiudere la finestra essa non risponderà . La view tenterà di invocare un metodo formato da MVC_NAME+”Closing”, quindi con nome “viewClosing”, nel controllore per notificarlo del tentativo di chiusura della finestra. Questo è corretto in quanto è il controllore a dover gestire gli eventi. Quindi, per completare l‘esempio del secondo listato, aggiungiamo il seguente metodo
public void viewClosing(){
view.dispose();
System.exit(0);
}
In questo modo la view avrà la possibilità di notificare il controller delle operazioni richieste dall‘utente. Stesso discorso vale per i bottoni a cui non vanno aggiunti ActionListener ma semplicemente implementato un metodo che abbia per prefisso l‘MVC_NAME del bottone seguito dalla stringa “Pressed”. In generale tutti i componenti che inviano “segnali” come la pressione di un bottone o il cambio di selezione di un tab, invocano metodi che hanno come nome l‘MVC_NAME seguito da una stringa predefinita (consultare la documentazione per gli altri componenti).
Conclusioni
Abbiamo visto come usando TikeSwing è possibile semplificare e migliorare una parte dello sviluppo di una applicazione desktop. A prima vista può sembrare inutile o addirittura più lungo il tempo di sviluppo ma vi assicuro che il codice sarà molto più ordinato e sicuramente molto più semplice da aggiornare. Nel prossimo articolo tratteremo in modo più approfondito la parte di gestione degli eventi, vedremo come sia possibile “tracciare” le modifiche fatte da un utente in una view e come far dialogare i vari controller tra di essi in modo del tutto naturale. Introdurremo inoltre l‘uso di alcuni componenti come Tabelle, Liste e ComboBox e in che modo il framework ci semplifichi in modo significativo il loro utilizzo. Vedremo inoltre come sia semplice creare componenti personalizzati da integrare con il framework.
Riferimenti
[1] Tomi Tuomainen , “Use high-level MVC and POJOs with Swing”, JavaWorld.com, 06/20/05
http://www.javaworld.com/javaworld/jw-06-2005/jw-0620-tikeswing.html
[2] http://java.sun.com/blueprints/patterns/MVC-detailed.html
[3] Commons BeanUtils http://jakarta.apache.org/commons/beanutils/
[4] Gamma E. – Helm R. – Johnson R. – Vlissides I., “Design Patterns: Elements of Reusable Object-Oriented Software”
[5] http://java.sun.com/docs/books/tutorial/reflect/index.html
[6] TikeSwing http://sourceforge.net/projects/tikeswing