Il
paradigma MVC (Model - View - Controller) è una
strategia intuitiva e ampiamente accettata nella progettazione
UI, si tratti di web o di applicazioni client complesse.
In effetti, si tratta di uno standard de facto così
ben definito che "l'aderenza al paradigma MVC"
è spesso utilizzata come misura della qualità
di framework per interfacce utente, e anche come slogan
pubblicitario.
L'aspetto
tanto attraente nel MVC è che si tratta di un
sistema "naturale" per svolgere determinate
azioni: è semplicemente molto sensato suddividere
il codice per l'interfaccia utente in grafi di oggetti
che rappresentano i dati (la componente model), widget
specifiche per l'applicazione che effettuino la resa
grafica dell'output (le "viste", ossia la
componente view) e una serie di azioni eseguite in risposta
a cambiamenti nella view o nel model (la componente
controller). Si tratta delle tre principali preoccupazioni
che consentono di suddividere il codice in tre gruppi
in maniera logica, per una più facile comprensione
e una più agevole manutenzione dell'applicazione.
L'idea risulta intuitiva per chiunque abbia mai scritto
un'applicazione che implichi interazione con utenti.
Figura
1
Swing
(e AWT) hanno rappresentato i primi casi di MVC usato
in Java., sebbene, in maniera inaspettata, Swing rimanga
uno dei framework MVC più complicati e meno intuitivi,
con poche indicazioni sul modo corretto di usarlo. Questo
articolo tratta i problemi di progettazione delle applicazioni
con Swing e mostra dove e come applicare i princìpi
Model - View - Controller per risolverli.
Nota
su JStaple: Tutti gli esempi in questo articolo
sono tratti dal progetto JStaple (http://objectstyle.org/jstaple/).
JStaple si occupa dei problemi qui discussi ed è
ancora in gran parte in una fase di ricerca. È
possibile considerare questo articolo come un white
paper informale su JStaple.
Swing
visto da vicino: dove è il Model - View - Controller?
Rinfreschiamo
le idee al lettore con alcuni dati relativi a Swing.
Internamente, Swing implementa un tipo comune di MVC
detto model-delegate, che raggruppa le componenti view
e controller in un "delegato UI". Raramente
gli sviluppatori hanno a che fare con la parte view/controller
quando scrivono delle applicazioni. Al contrario, essi
lavorano con ciò che viene definito "componenti".
I componenti Swing in sé stessi non fanno parte
della triade MVC, ma funzionano piuttosto come mediatori
tra model, view e controller.
Figura
2
La
maggior parte del codice personalizzato consiste nella
creazione di model e nella costruzione di componenti
che rappresentino il layout, comunichino con i sottocomponenti
e sincronizzino le viste al model. I componenti che
risultano da tale programmazione contengono in genere
un mix di codice che si occupa dei compiti inerenti
la view (p.e.: la disposizione grafica dei sottocomponenti
o il disegno), inerenti il controller (eseguire determinate
azioni), e dei compiti attinenti al model (come il wrapping
degli oggetti del dominio nei modelli personalizzati).
Quindi,
se è vero che i componenti Swing svolgono un
ottimo lavoro nel separare il look-and-feel dalla logica
dell'applicazione, a livello dell'applicazione essi
perdono però una suddivisione "pulita"
nelle componenti MVC e finiscono per diventare un miscuglio
intricato di codice che esegue un certo numero di compiti
non collegati. I problemi diventano ancor più
grandi nelle applicazioni complesse in cui ciascun componente
può contenere decine di sottoelementi, ciascuno
dei quali con i suoi requisiti di gestione.
Chi
non è convinto che si tratti di un serio problema
può dare un'occhiata al codice di esempio di
un qualsiasi buon libro su Swing (che dovrebbe mostrare
il modo migliore per lo sviluppo in Swing). La classe
di un componente custom di una certa complessità
occupa di solito dalle 4 alle 8 pagine di codice Java.
Nella realtà operativa, i componenti assumono
dimensioni addirittura maggiori, dal momento che gli
sviluppatori devono gestire molti casi che negli esempi
dei libri sono omessi.
Problema
n. 1: I componenti dell'applicazione mescolano molto
"materiale" non strettamente collegato all'interno
della classe di un singolo componente, producendo un
codice di difficile comprensione e manutenzione. Seguire
i pattern Swing "raccomandati" non produce
un'applicazione "pulita" in termini MVC.
Modelli
personalizzati
I dati e le operazioni su tali dati arrivano in genere
all'applicazione UI sotto forma di oggetti "di
dominio" o "business". Un tipico scopo
dell'applicazione è di visualizzare tali dati
e di interagire con essi. Gli esempi seguenti mostrano
come tale interazione sia implementata in Swing.
Se
c'è un array di stringhe e occorre mostrarle
in un JComboBox:
String[]
choices = ..;
//
internally combo box wraps the array into DefaultComboBoxModel
// but as long as we don't have to deal with it, this
is OK.
JComboBox combo = new JComboBox(choices);
Niente
male; procediamo oltre. Che accadrebbe se c'è
un elenco di dipartimenti e occorre mostrarne i nomi
consentendo all'utente di selezionare uno di essi dall'elenco?
Sembra banale e solo appena più complesso del
primo esempio, no? Su, pensateci bene: non è
possibile farlo direttamente in Swing, ma occorre superare
diversi problemi. Un modo possibile potrebbe essere
il seguente:
public
class DepartmentsComboModel extends AbstractListModel
implements ComboBoxModel {
protected List departments;
protected Object label;
protected Map departmentsByLabel;
public Object getSelectedItem() {
return label;
}
public void setSelectedItem(Object item) {
this.label = item;
}
public Object getElementAt(int index) {
Department department = (Department)
departments.get(index);
return department.getName();
}
public int getSize() {
return (departments != null) ? departments.size() :
0;
}
public Department getSelectedDepartment() {
if (label == null || departmentsByLabel == null) {
return null;
}
return (Department) departmentsByLabel.get(label);
}
public List getDepartments() {
return departments;
}
public void setDepartments(List departments) {
this.departments = departments;
// index departments by name
departmentsByLabel = new HashMap();
...
fireContentsChanged(this, 0, departments.size());
}
public void setSelectedDepartment(Department department)
{
this.label = (department != null) ?
department.getName() :
null;
}
}
È
necessaria tutta questa intera classe per svolgere un
compito semplice: mostrare una proprietà di un
oggetto! Se ci sono altri combo box che devono mostrare
diversi tipi di oggetti o le varie proprietà
dello stesso tipo di oggetto, sarà necessario
creare altre ComboBoxModels, e la storia si fa davvero
lunga
La soluzione appena presentata è
il modo in cui "si suppone" che debba funzionare
il JComboBox ed è MVC puro al 100%. Appare comunque
come la cosa più innaturale da fare: se ci si
spostasse da JComboBox a JTable, il livello di complessità
del modello custom si innalza. Appare chiaro quindi
che esiste un "modello del dominio" che riguarda
effettivamente gli oggetti di cui ci occupiamo, e anche
un "modello" che è quello del MVC:
i due model non sono la stessa cosa.
Problema
n. 2: I componenti Swing non riescono facilmente
ad accedere ai dati a partire da un modello di dominio
arbitrario e necessitano di interfacce specifiche per
il modello; pertanto gli sviluppatori finiscono per
scrivere innumerevoli adattatori per le classi di dominio
esistenti.
Aggiornare il model: quando gli eventi diventano problematici
La maggior parte degli elementi dei form con cui si
immettono i dati non fanno niente di strano con questi
dati. In genere viene richiesto solamente di aggiornare
una proprietà di un qualche oggetto del model
con il valore immesso, magari effettuando prima anche
una validazione. A meno che non sia coinvolto un wrapper
per il model, come si è visto prima, la procedura
di aggiornamento delle proprietà non è
automatica. Un componente deve implementare e installare
un listener per eventi widget, e tale listener deve
essere conforme a interfacce predefinite. Se desideriamo
modificare il nome dei dipartimenti usando un JTextField,
l'input utente deve essere catturato usando codice simile
al seguente:
final
Department department = ...;
final JTextField nameField = ...;
...
nameField.getDocument().addDocumentListener(
new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
department.setName(nameField.getText());
}
public void removeUpdate(DocumentEvent e) {
department.setName(nameField.getText());
}
public void changedUpdate(DocumentEvent e) {
department.setName(nameField.getText());
}
});
E
questo codice deve essere copiato e incollato per ciascun
campo testo sullo schermo... E va notato che qui non
stiamo scrivendo un programma che deve sostituire MS
Word, ma vogliamo semplicemente aggiornare la proprietà
di un oggetto con il testo digitato da un utente dentro
un campo testo. Alla fine ci ritroviamo con un proliferare
di classi interne, con problemi di inizializzazione
in totale confusione, che infrangono o perlomeno indeboliscono
il design OO e che è semplicemente orribile.
Lo stesso vale per tutti gli altri widget Swing: JButtons,
JTextAreas, etc.
Problema
n. 3: I componenti Swing devono registrarsi come
listener di eventi anche per i compiti più semplici.
Dal momento che ciascun listener deve conformarsi a
una interfaccia specifica, il codice viene contaminato
da classi interne anonime.
Ripulire
il tutto: MVC sopra Swing
La discussione condotta sopra suggerisce che l'esperienza
dello sviluppatore con Swing è ben lontana dall'ideale.
Dal momento che riscrivere Swing è fuori questione,
non sarebbe male costruirgli sopra qualcosa che lo renda
più usabile. In particolare, richiedono un reale
miglioramento le seguenti aree: i componenti necessitano
di una scomposizione ulteriore e di un modo semplificato
per interagire con il modello di dominio. Ebbene, risolvere
questi problemi significa creare ancora un altro framework.
Nota
sui framework per le applicazioni: vale la pena
notare che qualsiasi applicazione a interfaccia grafica
utente più complessa di "Hello World"
avrà probabilmente bisogno di infrastrutture
extra non fornite direttamente a Swing (o Java). La
più importante è un container per l'applicazione,
che fornisca una serie coerente di servizi per i componenti.
La nozione di application object è qualcosa che
manca totalmente in Swing. Implementare una soluzione
basata su un container IoC come Spring o Pico dovrebbe
indirizzarsi verso questo problema; si tratta tuttavia
di un argomento diverso, e per questo l'articolo si
concentrerà sul miglioramento del modello a componenti
di Swing.
Figura
3
Per
affrontare i problemi esposti suggeriamo di suddividere
i componenti Swing secondo i princìpi del MVC
(i framework web WebObjects e Tapestry affrontano il
problema con un approccio molto simile a quello qui
illustrato). Nel nostro design, i tradizionali componenti
Swing assumono il ruolo di View e vengono privati di
tutto il codice che riguarda Model e Controller. La
vista comprende solamente la disposizione grafica dei
sottocomponenti e/o il disegno. Il Model può
essere rappresentato da una classe Java, in maniera
che gli oggetti del dominio possano essere impiegati
direttamente. La parte più importante è
il Controller, che consiste di due parti: una classe
contenente tutta la logica custom per il componente
e uno strato dichiarativo di collegamento. Il resto
dell'articolo mostra il modo in cui le diverse parti
contemplate in questo design funzionano insieme, in
nell'implementazione del prototipo di JStaple.
La
View: java.awt.Component
Come accennato sopra, la View è di solito una
sottoclasse di java.awt.Component. Può trattarsi
di un widget Swing come JButton (non sono richieste
ulteriori sottoclassi) oppure di un componente custom.
Si tratta di una "vista" dalla prospettiva
dell'applicazione ma, sotto tutti gli altri aspetti,
si tratta di un normale componente Swing/AWT. Ecco un
semplice esempio che dimostra come la vista si occupi
solamente della disposizione dei sottocomponenti e non
includa alcun listener custom, modello o riferimento
diretto ai dati dell'applicazione:
public
class MainFrameView extends JFrame {
protected JButton newDepartmentButton;
protected JComboBox departmentsCombo;
protected JLabel departmentsLabel;
public MainFrameView() {
// create widgets
departmentsLabel = new JLabel("Departments:");
newDepartmentButton = new JButton("New Department...");
departmentsCombo = new JComboBox();
getContentPane().setLayout(new BorderLayout());
// layout subcomponents, not shown here, as this is
normal Swing code
// ...
setSize(600, 400);
}
public JButton getNewDepartmentButton() {
return newDepartmentButton;
}
public JComboBox getDepartmentsCombo() {
return departmentsCombo;
}
public JLabel getDepartmentsLabel() {
return departmentsLabel;
}
}
Il Controller: implementare la logica
dell'applicazione in STComponent
La parte del controller STComponent è il luogo
in cui codificare la logica personalizzata di un componente
grafico. Ha la funzione di facciata per un qualsiasi
numero di oggetti del dominio che vengano dichiarati
come proprietà bean di lettura/scrittura del
STComponent. STComponent può inoltre dichiarare
diversi metodi action. Ecco un esempio semplificato
di STComponent. Analogamente all'esempio di view presentato
prima, non contiene particolari listener o modelli,
ma semplicemente alcuni oggetti del dominio (questo
esempio mostra riferimenti a oggetti persistenti e l'uso
di DataContext di Cayenne per l'accesso al database)
e metodi action:
public
class MainFrame extends BaseComponent {
// declare domain objects needed for this component
as ivars
protected DataContext dataContext;
protected List departments;
protected Department selectedDepartment;
public MainFrame(Component view) {
super(view);
}
// simple and "calculated" properties...
public List getDepartments() {
return departments;
}
public Department getSelectedDepartment() {
return selectedDepartment;
}
public void setSelectedDepartment(Department
selectedDepartment) {
this.selectedDepartment = selectedDepartment;
}
public String departmentLabel(Department department)
{
return " - " + department.getName();
}
// actions
public void addDepartmentAction() {
// create new department and pass control to the
DepartmentEditor dialog
Department department = (Department) dataContext
.createAndRegisterNewObject(Department.class);
DepartmentEditor editor = new DepartmentEditor(
new DepartmentEditorView(),
this,
department);
// binding a callback action to the dialog.
// bindings are discussed in detail below.
editor.bindOkAction("departmentAddedAction(#actionParameter)");
editor.startupAction();
}
// ....
}
Il
Controller: collegare tutti gli elementi
Né la vista, né STComponent mostrati sopra
contengono codice per comunicare l'una con l'altro e,
ovviamente, il modello del dominio è completamente
indipendente da entrambi. I compiti di comunicazione
sono svolti dallo strato di collegamento (il binding)
che gestisce tutti i legami senza bisogno di creare
infiniti adattatori personalizzati. Internamente, lo
strato di collegamento consiste di adattatori generici
per i componenti Swing che consentono "l'inserimento"
di certe proprietà del model o di action del
controller alle "prese" conosciute. Un altro
termine per tale tipo di accoppiamento plug/socket ("spina/presa")
è binding ("collegamento"). Nel caso
più semplice, un binding stabilisce un canale
di dati tra la proprietà "legata" della
view e alcune proprietà del model, ma senza la
necessità di interfacce speciali per il model
o il listener. Allo stesso tempo la View e il Model
non hanno consapevolezza l'una dell'altro, dal momento
che tutte le comunicazioni sono indirette. Ciò
salva la fondamentale esigenza di separazione delle
componenti del paradigma MVC.
Ciascun
componente è collegato tramite un binding set,
ovvero un certo numero di legami predefiniti caratterizzati
da nome, alcuni dei quali sono obbligatori, altri facoltativi.
L'effettiva implementazione di un binding set viene
fornita da JStaple e dipende dalla vista specifica (dal
momento che internamente un binding deve usare una qualunque
API fornita da Swing, comprese classi interne anonime
e model specializzati), ma la API utilizzata all'interno
del controller per lavorare con i binding è la
stessa, indipendentemente dal meccanismo sottostante.
Inoltre, è possibile fare di meglio che connettere
semplicemente la proprietà del model a una proprietà
della vista. Come sarà mostrato sotto, esistono
diversi tipi di binding (p.e.: collegare action di STComponent
a certi eventi della view, etc.). I binding sono resi
veramente dinamici aggiungendo al mix un linguaggio
di script. Per lo script, l'attuale implementazione
di JStaple utilizza OGNL.
Lo
strato di collegamento è dichiarativo e separato
da STComponent. JStaple possiede un prototipo di binding
pienamente funzionante che è configurato all'interno
di STComponent. Tuttavia, dovrebbe essere facile estrarre
tutti i binding in XML separato. In questo modo, i collegamenti
possono essere modificati e ricaricati dinamicamente
senza effettuare ricompilazioni, oltretutto - ed è
ancor più importante - senza contaminare il codice.
Ecco
un esempio di binding a un JComboBox che si trova in
un componente custom. Dimostra diversi risultati che
è possibile ottenere con i collegamenti. Va notato
che non c'è alcun bisogno di creare sottoclassi
di JComboBox, dal momento che il binding funziona con
tutti i widget Swing standard (oltre che con componenti
personalizzati).
BindingBuilder
builder = new BindingBuilder(controllerObject);
builder.switchToSubview("departmentsCombo");
builder.bindToPullValuesFromModel(STComboBoxBindings.LIST_BINDING,
"departments");
builder.bindToPullValuesFromModel(STComboBoxBindings.LABEL_BINDING,
"departmentLabel(#item)");
builder.bindForTwoWaySync(STComboBoxBindings.SELECTED_VALUE_BINDING,
"selectedDepartment");
builder.bindAction(STComboBoxBindings.ACTION_BINDING,
"showEmployeesAction()");
builder.bindToPullValuesFromModel("visible",
"connected");
Adesso
addentriamoci nelle singole parti:
BindingBuilder
builder = new BindingBuilder(controllerObject);
builder.switchToSubview("departmentsCombo");
Viene
creato un BindingBuilder che è semplicemente
una classe helper per nascondere i dettagli interni
del binding. La seconda riga indica al costruttore che
si sta per iniziare la configurazione di una proprietà
departmentCombo della vista. Ci si attende che sia un
JComboBox.
builder.bindToPullValuesFromModel(STComboBoxBindings.LIST_BINDING,
"departments");
Qui
viene impostato uno dei binding "standard"
di JComboBox. I nomi dei binding standard per i diversi
componenti Swing sono definiti nelle corrispondenti
interfacce di JStaple. Per esempio, questo e altri binding
di JComboBox sono definiti nell'interfaccia STComboBoxBindings.
Il secondo parametro per un metodo è il nome
della proprietà del model, che in questo caso
è "departments". Le proprietà
vengono risolte utilizzando STComponent come oggetto
"root". Dal momento che il binding è
creato usando il metodo "bindToPullValuesFromModel",
esso lavora a senso unico, aggiornando automaticamente
la view ogni volta che il model cambia.
builder.bindToPullValuesFromModel(STComboBoxBindings.LABEL_BINDING,
"departmentLabel(#item)");
La
riga sopra mostra come sia facile costruire un'etichetta
da mostrare per un determinato oggetto nell'elenco,
in una maniera definita dal controller per questa vista
specifica. Una stringa, mostrata in un JComboBox non
deve essere necessariamente il risultato di department.toString(),
o una proprietà della classe Department (sebbene
in alcuni casi possa esserlo). È possibile fare
di meglio. Con una piccola porzione di script OGNL usato
come secondo argomento possiamo delegare all'oggetto
controller una decisione sulla stringa da mostrare:
L'effettiva
implementazione del metodo departmentLabel da parte
del controller potrebbe, per esempio, essere posta come
prefisso al nome di un determinato dipartimento separandola
con un trattino:
public
String departmentLabel(Department department) {
return " - " + department.getName();
}
Ecco
la riga successiva:
builder.bindForTwoWaySync(STComboBoxBindings.SELECTED_VALUE_BINDING,
"selectedDepartment");
Questa
riga mostra come sincronizzare l'oggetto selezionato
tra JComboBox e il model. L'oggetto che viene passato
è un Department (dal momento che questo è
il tipo di oggetti nell'elenco collegato), non l'etichetta
String mostrata nel box combo. Non è necessario
che lo sviluppatore effettui una conversione manuale
tra Department e l'etichetta string: si tratta di una
conversione di cui si occupa JStaple.
Il binding precedente imposta la proprietà Department
del controller, ma è possibile anche collegare
un metodo action che deve essere invocato ogni volta
che ci siano modifiche al dipartimento selezionato.
Ecco come è possibile fare:
builder.bindAction(STComboBoxBindings.ACTION_BINDING,
"showEmployeesAction()");
L'interfaccia
STComboBoxBindings dichiara solo qualche binding specifico
per JComboBox. Allo stesso tempo, un tipico JComponent
potrebbe possedere decine di proprietà: potrebbe
darsi il caso che alcune debbano essere controllate
dall'applicazione. Ciò ci conduce a scoprire
un'altra interessante caratteristica: il binding a una
proprietà arbitraria. L'esempio che segue mostra
come impostare un binding che modifica la visibilità
di JComboBox sulla base della proprietà "connected"
del model:
builder.bindToPullValuesFromModel("visible",
"connected");
L'esempio
JComboBox sopra riportato non appare più come
Swing. Presenta caratteristiche di pulizia e non contiene
classi interne anonime o modelli specializzati. Ribadiamo
quindi ciò che è possibile ottenere usando
i binding.
I
binding sono la parte del controller usata per la comunicazione
indiretta (p.e.: passare i dati o invocare un'azione)
tra
- a.
view e model
- b.
view e STComponent
I
binding forniscono un'infrastruttura che consente al
design MVC, pulito e gerarchico, di usare Swing, AWT
e componenti custom. Ciascun elemento view (vale a dire,
qualsiasi widget Swing o custom) può avere una
serie predefinita di binding. Anche i componenti utente
possono dichiarare binding). Per esempio, una finestra
di dialogo può avere due binding: okAction e
cancelAction. A seconda del bottone della finestra di
dialogo che viene premuto, verrà attivato uno
di essi quando la finestra di dialogo sarà chiusa,
con la conseguente invocazione del metodo dell'azione
collegato dell'oggetto che ha effettuato la chiamata.
Tutto questo è possibile senza dover definire
interfacce listener speciali o dover conoscere qualcosa
su ciò che effettua la chiamata. I binding per
un determinato componente sono indipendenti l'uno dall'altro,
quindi non c'è la necessità di un model
specializzato per ciascun tipo di componente. Al contrario,
diverse parti del modello di dominio possono essere
collegate tramite differenti binding, con il risultato
di una notevole flessibilità.
Se un binding non viene riconosciuto come standard,
il nome del binding viene trattato come una proprietà
della vista. Dal punto di vista della view, i binding
possono essere di tipo pull, push o "a doppia modalità".
Le action di STComponent possono essere collegate in
maniera semplice, proprio come valori di proprietà.
Analogamente ai sistemi basati sugli eventi, il binding
disaccoppia la view dal model, sebbene, quanto a questo,
il binding vada molto oltre. A differenza che con il
MVC basato sugli eventi, i binding non richiedono speciali
interfacce per il model o classi interne anonime.
Ci
sono altri aspetti in cui il binding può venire
in aiuto e che non sono ancora stati esplorati completamente
in JStaple. Per esempio, il binding può fornire
dei generici punti di aggancio per la validazione dell'input.
Dovrebbe essere possibile installare un oggetto validatore
che venga invocato ogni volta che un binding spinge
un valore dalla view al model. I fallimenti nella validazione
possono essere gestiti in maniera consistente, per esempio
modificando il colore dell'elemento vista e impostando
un messaggio per la validazione.
E
gli eventi?
Ogni componente in Swing fornisce un modo per registrare
i listener interessati alla notifica dei vari "avvenimenti"
che si verifichino all'interno del componente. Si tratta
di un'ottima funzionalità, dal momento che garantisce
grande flessibilità. Tuttavia, l'applicazione
nel suo insieme raramente ha necessità di essere
messa al corrente che una finestra di spunta è
stata riempita o che un testo è stato digitato
all'interno di un determinato campo. Tali eventi generati
da widget possono essere definiti "a corto raggio":
solo il controller corrispondente ha realmente interesse
ad essi.
D'altro
canto ci sono eventi a livello di applicazione in genere
associati con cambiamenti nel model. Sono piuttosto
diversi dai normali eventi Swing e sono di solito specifici
dell'applicazione. Questi eventi possono comunque avere
la loro origine in un click su un bottone o in qualche
altra azione nella view, ma è probabile che questo
fatto non sia riflesso nell'oggetto evento e non sia
importante per i listener. Si tratta degli eventi "a
lungo raggio", che possono risultare di interesse
per più di un solo componente.
Il
comportamento qui suggerito è di sostituire ogni
uso esplicito di eventi "a corto raggio" a
livello dei componenti con binding per rendere tutto
più semplice e flessibile. Ma un'applicazione
più complessa dovrebbe comunque essere progettata
intorno a eventi a "lungo raggio" che forniscano
una comunicazione non troppo stretta tra le varie parti
del programma. Ovviamente, è sempre possibile
usare insieme al binding anche eventi "a raggio
corto", se necessario, dal momento che i binding
di JStaple sono semplicemente degli adattatori a una
serie standard di widget fornita da Swing.
Nascondere
eventi di widget a basso livello è semplicemente
una delle funzioni che i binding possono svolgere. Possono
anche aiutare con l'innesco di eventi "a lungo
raggio": un binding può essere configurato
per innescare un evento "a lungo raggio" ogni
volta che una vista spinga un valore al model (in JStaple
questa funzionalità ancora non è implementata).
Questo approccio ha due aspetti positivi: è possibile
codificare un componente senza conoscere quali eventi
esso genererà (dal momento che ciò è
definito dinamicamente dagli utenti del componente a
runtime), e non è necessario decorare il modello
affinché inneschi eventi in base ai cambiamenti
di una proprietà.
Framework
simili
Scope (http://scope.sourceforge.net/). Nella versione
1.0.* conosciuta dall'autore, Scope contiene molti dei
concetti discussi qui sotto il nome comune di MVC gerarchico
(Hierarchical Model View Controller, HMVC). Tuttavia,
Scope presenta anche diverse differenze: specificamente
il design del binding è meno dinamico, non supporta
uno script avanzato, e richiede la creazione di sottoclassi
di widget Swing standard. Le action e le proprietà
sono trattate come elementi separati (detti "controlli"
e "selettori"). Sembra che non supporti l'estrazione
dei binding in uno strato separato.
Cocoa per Mac OS X. Implementato in Objective C. Esiste
un wrapper Java, ma può essere usato solo sul
Mac OS X.
Conclusioni
L'articolo presenta i principali inconvenienti di Swing
che lo rendono così difficile da usare: (1) la
mancanza di separazione tra il codice che gestisce gli
aspetti interni di Swing e la logica dell'applicazione,
e (2) la mancanza di un modo pulito per connettere il
model del dominio con i componenti.
La
soluzione suggerita è di aggiungere uno strato
MVC separato sopra Swing, trattando i componenti Swing
come una "view". All'interno di questo strato,
vengono usati dei binding dinamici, in cui è
possibile inserire degli script, per l'assemblaggio
dichiarativo e la comunicazione tra le varie parti correlate
dell'applicazione.
Il
codice JStaple con tutti gli esempi mostrati in questo
articolo può essere scaricato dall'indirizzo
http://objectstyle.org/jstaple/.
L'autore
Andrei (Andrus) Adamchik è uno dei fondatori
e dei principali sviluppatori di Cayenne, un framework
di mapping object/relational. Nel suo lavoro come programmatore
e architetto di software, ha creato applicazioni enterprise,
per un certo numero di aziende del campo logistico,
finanziario, multimediale e dell'intrattenimento. Attualmente
Andrus è CEO della ObjectStyle LLC, una azienda
di consulenze software con sedi a New York e Atlanta.
Andrus ha dato inizio al progetto JStaple come prototipo
per semplificare il difficile sviluppo in Swing grazie
agli strumenti di modellazione di Cayenne.
|