Pattern architetturali – seconda parte
Scritto da
Marco Amendola
il
mercoledì 1 dicembre 2010
Linguaggio:
•
Framework:
•
Livello: 200
Un'applicazione è una struttura complessa, la cui
costruzione non può essere affidata al caso: le varie parti devono
interagire correttamente e in modo controllabile. Vengono esaminati
i vari pattern architetturali affermatisi, nel corso del tempo, su
questa tematica. Leggi la
prima parte dell'articolo.
Model-View-ViewModel
Uno dei pattern da subito emersi per la piattaforma WPF è
l'ormai celeberrimo "Model-View -ViewModel" (MVVM), introdotto da
John Gossman durante lo sviluppo di WPF, ma derivato dal pattern
"Presentation Model" di Martin Fowler [1] (del
quale MVVM è di fatto una specializzazione "idiomatica" per WPF e
Silverlight).
Nonostante questo pattern sia da tempo ben delineato nei suoi
principi fondamentali, c'è voluto del tempo affinché questi ultimi
venissero declinati in modi comprensibili ed efficaci.
Credo che il problema fondamentale fosse la carenza di vere
implementazioni concrete del pattern, applicate nella soluzione di
problematiche reali e non su esempi accademici, i quali hanno il
pregio della chiarezza ma sono liberi dalla complessa articolazione
e dalla necessità di compromessi tipiche delle applicazioni
reali.
Cos'è, quindi, il pattern MVVM? Perché se ne parla come un
"must" nell'ambito della programmazione con tecnologie WPF e
Silverlight?
Caratteristiche
Il cuore del funzionamento di questo pattern è la creazione di
un componente (ViewModel) che rappresenta, in modo astratto, tutte
le informazioni e i comportamenti della corrispondente View;
quest'ultima si limita a visualizzare graficamente quanto esposto
dal ViewModel, a riflettere i propri cambi di stato nel ViewModel
stesso oppure ad attivare suoi comportamenti.
L'uso di questo pattern richiede la presenza, nella tecnologia
di UI, di un'ottimo meccanismo di data-binding (tipicamente
bidirezionale) o, in alternativa, di un strato di sincronizzazione
fra la View e il ViewModel.
E' compito del ViewModel, però, offrire alla View una
"superficie esterna" il più possibile ben fruibile, in modo che la
sincronizzazione dello stato possa essere fatta senza introdurre
logiche decisionali che rendano necessario un test specifico.
Il processo di interazione si svolge secondo questo schema:
Schema di interazione in MVVM
- L'utente interagisce con la View;
- la variazione di stato della View viene "riflessa" (tipicamente
attraverso meccanismi di binding) nel ViewModel; in alternativa,
attraverso meccanismi di invocazione indiretta (ad esempio
Command), viene attivata una determinato metodo del ViewModel;
- in risposta al cambio di stato o all'attivazione del metodo, il
ViewModel intraprende le opportune azioni sul Model e
modifica il proprio stato;
- il nuovo stato del ViewModel viene rappresentato nella View in
virtù del binding.
E' molto importante sottolineare che il
ViewModel mantiene, nel proprio stato, non solo le informazioni
attinenti il dominio business, ma anche la condizione corrente
della visualizzazione (ad esempio: stato di attivazione di un
determinato controllo, selezione attiva in un combobox, ecc.).
Questo consente di includere completamente il comportamento
della porzione di applicazione gestito in una classe (ViewModel)
testabile e del tutto disaccoppiata dalla relativa View.
Grazie a questa forte separazione è possibile decidere la
completa sostituzione della View (o, più frequentemente, un cambio
anche radicale del "Look & Feel") senza praticamente alcuna
conseguenza sulla componente di logica applicativa.
Questo pattern, come già anticipato nell'introduzione, è in
realtà il "Presentation Model" definito da Fowler, nella sua
declinazione specifica su ambienti WPF e Silverlight
("implementazione idiomatica").
Il pattern MVVM è particolarmente adatto all'applicazione su WPF
e Silverlight, in quanto presenta una "integrità concettuale"
rispetto a queste tecnologie. I controlli nativi di WPF e SL si
basano su un meccanismo identico al MVVM: sono infatti "lookless",
ovvero le corrispondenti classi ne definiscono solo il
comportamento e la semantica, mentre l'aspetto viene "applicato"
dall'esterno mediante un template XAML.
Di seguito i grafici UML relativi alla struttura delle classi in
gioco e delle interazioni fra le istanze:

MVVM: Class diagram

MVVM: Sequence diagram
"View (of the) Model" oppure "Model (of the) View"?
Questa domanda (presa in prestito da un illuminante post di Rob
Eisenberg) è probabilmente la chiave per capire il senso di MVVM:
il ViewModel rappresenta semplicemente una facciata "UI-friendly"
del (Domain) Model, oppure è un'astrazione dell'interfaccia utente
indipendente dalla particolare tecnologia di visualizzazione?
La maggior parte degli esempi di utilizzo di MVVM, alcuni
framework a molti articoli pongono l'accento sulla prima
interpretazione, considerando il ViewModel come un Adapter tra il
dominio business e la tecnologia grafica. Questa però è una visione
riduttiva, che rischia di non far emergere il vero ruolo centrale
del ViewModel: supportare la UI mediante un'astrazione (un modello,
per l'appunto) che prenda il controllo di tutto quello che succede
all'interno di ciascuna View ma anche, allontanando il punto di
vista, dell'intera applicazione.
Un modo per visualizzare il concetto è pensare all'applicazione
come composta da un albero di ViewModel, ciascuno responsabile di
una particolare "zona" dell'interfaccia utente, che realizzi nel
suo insieme una sorta di macchina a stati: ogni volta che l'utente
sollecita l'applicazione, e quindi indirettamente la "macchina",
quest'ultima reagisce, cambiando il proprio stato ed eseguendo
sotto il proprio controllo le operazioni di dominio business.
In questa visione, la View si riduce ad un "vetro" attraverso il
quale l'utente osserva il funzionamento della "macchina": chi ha
utilizzato Microsoft Office Automation potrebbe notare
qualche similitudine...
E' naturalmente possibile, se non frequente, che in alcuni casi
il ViewModel possa somigliare molto agli oggetti del (Domain)
Model. D'altra parte sono proprio questi oggetti il tema principale
dell'applicazione: è perfettamente normale che l'utente ne possa
visualizzare e modificare gli attributi; di conseguenza la loro
interfaccia può (e deve, in qualche misura) influenzare
l'interfaccia dei ViewModel che li espongono alla View.
La strategia probabilmente più conveniente, nei casi più
complessi, è quella di utilizzare, per una singola View, un View
Model composto da più classi:
- una classe che supporti il ciclo di vita della View
(inizializzazione, conferma chiusura, chiusura e rilascio) e la
gestione delle azioni dell'utente (caricamenti asincroni,
selezione, salvataggio);
- una (o più) classi, rette dalla precedente, che supportino la
View nel rappresentare visualmente gli oggetti di dominio
business.
Comportamento e "Look & Feel"
Uno dei vantaggi di utilizzare un approccio del genere,
specialmente con tecnologie WPF e SL, risiede nella naturalezza con
cui si ottiene la separazione del comportamento dell'applicazione
dal suo "Look & Feel"; questo è estremamente vantaggioso in
scenari di sviluppo (ultimamente sempre più diffusi) in cui gli
aspetti di User Experience sono curati da figure con precise
competenze, diverse da quelle necessarie per lo sviluppo e la
codifica.
Per facilitare questa separazione, il ViewModel deve essere
progettato in termini il più possibile astratti; anche per questo
motivo è preferibile evitare dipendenze dirette alla View stessa,
oppure verso componenti specifici della tecnologia di UI.
Un altro accorgimento che aiuta a focalizzarsi sull'astrazione
piuttosto che sulla rappresentazione è quello di scegliere i nomi
di metodi e proprietà in modo da descrivere stati e comportamenti
da un'ottica relativa all'applicazione, e non ai controlli
coinvolti (ad es.: IsMyActionApplicable invece che
IsButtonEnabled).
Si tratta, rispetto all'uso di UI event-driven basate sui
controlli, di uno "shift" mentale non trascurabile.
Command
Il meccanismo dei Command è sicuramente una delle novità
interessanti introdotte da WPF (ne parla Pietro Libro in questo
articolo).
Il pattern su cui si basa questo meccanismo, ad onor del vero, era
già ben definito nel notissimo saggio "Design Patterns: Elements of
Reusable Object-Oriented Software" e da tempo utilizzato nelle
architetture di UI (ad esempio MVP).
La potenza di questo pattern, e della sua integrazione nella
tecnologia di UI, è la possibilità di gestire gli input dell'utente
ad un più alto livello di astrazione, che identifichi le
finalità dell'azione dell'utente a prescindere
dalla gesture utilizzata per avviarle.
L'importanza dei Command all'interno di MVVM è costituita
principalmente, a mio avviso, dalla possibilità di utilizzare il
meccanismo dichiarativo del databinding anche per la comunicazione
di messaggi dalla View al ViewModel; attraverso il databinding,
infatti, è possibile collegare i controlli a dei Command esposti
dal ViewModel.
Composizione della UI
E' piuttosto comune per le applicazioni moderne fare uso della
cosiddetta UI composition, ovvero della capacità di
comporre l'interfaccia utente mediante la creazione dinamica di
diversi parti più piccole, spesso coordinate, collocate all'interno
di opportune zone di una "impalcatura" principale, detta
shell. Un classico esempio (probabilmente tra i più
complessi) di questa tecnica è la UI di Visual Studio, composta da
una numerosa serie di pannelli, toolbar e finestre di documento,
completamente personalizzabile dall'utente ed estendibile con nuovi
componenti forniti mediante plugin.
WPF e SL supportano e incoraggiano la composizione dinamica
della UI, attraverso l'utilizzo del databinding e dei
DataTemplates.
Oltre all'aspetto puramente "visuale", tuttavia, la UI
Composition richiede la presenza di qualche tipo di infrastruttura
che regoli e diriga il ciclo di vita (creazione, inizializzazione,
attivazione, disattivazione, rilascio) delle varie porzioni di
schermo.
Il pattern MVVM non prescrive una linea precisa per questi
aspetti; le diverse implementazioni, comunque, sono suddivise in
due gruppi principali:
- View-First: il processo di composizione è guidato e sollecitato
dalla View; quest'ultima, cioè, stabilisce quali parti debbano
essere composte e in quale zona della shell siano visualizzate.
Questa impostazione richiede che i ViewModel associati a ciascuna
parte della View siano istanziati e collegati in fase
successiva;
- Model-First: la composizione è gestita istanziando prima di
tutto il ViewModel e collegando successivamente la View
corrispondente. In questa impostazione, che ritengo più naturale ed
in linea con la filosofia del MVVM, la composizione avviene prima
di tutto a livello del ViewModel, mediante la creazione (anche
dinamica) di un "albero" delle varie parti; alla View è lasciato il
compito di rappresentare questo albero e le sue variazioni
utilizzando gli usuali meccanismi di binding e templating.
La presenza di un modello che rappresenta l'applicazione e la
versatilità del binding WPF/SL rendono semplice l'aggiornamento
coordinato di diverse parti dell'interfaccia utente.
E' importante notare la somiglianza di questa impostazione con
quella utilizzata da WPF stesso per la costruzione della UI, che si
basa sul mantenimento di un Logical Tree (per rappresentare il
contenimento degli elementi) e di un Visual Tree (per gestirne la
rappresentazione con elementi visuali).
[1] Martin Fowler è
un famosissimo autore di testi relativi all'analisi e al design di
software "object oriented". Sono particolarmente noti, tanto da
costituire un "vocabolario" di termini e concetti universalmente
accettato, i suoi studi e le sue classificazioni dei maggiori
pattern architetturali usati nelle applicazioni
enterprise.
Riferimenti e risorse
- John Gossman:
- Martin Fowler
- Rob Eisenberg
- Jeremy D. Miller
- Ward Bell
- Josh Smith
- Oleksiy Shelest
- Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides
Tags: mvvm,patterns,architecture,UI