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 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: Class diagram

MVVM: Sequence 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


Tags: mvvm,patterns,architecture,UI

 
x