MVVM è un cerotto per livelli di associazione dati mal progettati. In particolare, ha visto molto uso nel mondo WPF / silverlight / WP7 a causa delle limitazioni nell'associazione dei dati in WPF / XAML.
D'ora in poi, assumerò che stiamo parlando di WPF / XAML poiché ciò renderà le cose più chiare. Vediamo alcune delle carenze che MVVM intende risolvere in WPF / XAML.
Forma dei dati vs forma dell'interfaccia utente
La "VM" in MVVM crea una serie di oggetti definiti in C # che si mappano su una serie di oggetti di presentazione definiti in XAML. Questi oggetti C # sono in genere collegati a XAML tramite le proprietà DataContext sugli oggetti di presentazione.
Di conseguenza, il grafico dell'oggetto viewmodel deve essere mappato sul grafico dell'oggetto di presentazione dell'applicazione. Questo non vuol dire che la mappatura debba essere una a una, ma se un controllo di elenco è contenuto da un controllo di finestra, allora ci deve essere un modo per passare dall'oggetto DataContext della finestra a un oggetto che descrive i dati di tale elenco.
Il grafico a oggetti viewmodel disaccoppia correttamente il grafico a oggetti modello dal grafico a oggetti dell'interfaccia utente, ma a spese di un layer viewmodel aggiuntivo che deve essere creato e gestito.
Se voglio spostare alcuni dati dalla schermata A alla schermata B, ho bisogno di giocare con i modelli di visualizzazione. Nella mente di un uomo d'affari, questa è una modifica dell'interfaccia utente. Esso dovrebbe avvenire esclusivamente nel mondo di XAML. Purtroppo, raramente può. Peggio ancora, a seconda di come sono strutturati i modelli di visualizzazione e di come i dati cambiano attivamente, potrebbe essere necessario un certo reindirizzamento dei dati per realizzare questa modifica.
Aggirare i dati inesprimibili
I binding WPF / XAML non sono sufficientemente espressivi. Fondamentalmente puoi fornire un modo per raggiungere un oggetto, un percorso di proprietà da attraversare e associare i convertitori per adattare il valore della proprietà dei dati a ciò che richiede l'oggetto di presentazione.
Se hai bisogno di associare una proprietà in C # a qualcosa di più complesso di quello, sei praticamente sfortunato. Non ho mai visto un'app WPF senza un convertitore di associazione che trasformava true / false in Visible / Collapsed. Molte app WPF tendono anche ad avere qualcosa chiamato NegatingVisibilityConverter o simile che inverte la polarità. Questo dovrebbe essere suonare campanelli d'allarme.
MVVM fornisce le linee guida per strutturare il codice C # che può essere utilizzato per ovviare a questa limitazione. Puoi esporre una proprietà sul tuo modello di vista denominata SomeButtonVisibility e limitarla alla visibilità di quel pulsante. Il tuo XAML è bello e carino ora ... ma ti sei trasformato in un impiegato - ora devi esporre + aggiornare i binding in due punti (l'interfaccia utente e il codice in C #) quando la tua interfaccia utente si evolve. Se hai bisogno dello stesso pulsante per essere su un altro schermo, devi esporre una proprietà simile su un modello di visualizzazione a cui tale schermo può accedere. Peggio ancora, non posso semplicemente guardare XAML e vedere quando il pulsante sarà più visibile. Non appena le associazioni diventano leggermente non banali, devo svolgere un lavoro investigativo nel codice C #.
L'accesso ai dati ha una portata aggressiva
Poiché i dati generalmente entrano nell'interfaccia utente tramite le proprietà DataContext, è difficile rappresentare i dati globali o di sessione in modo coerente in tutta l'app.
L'idea dell '"utente attualmente connesso" è un ottimo esempio: spesso è una cosa veramente globale all'interno di un'istanza della tua app. In WPF / XAML è molto difficile garantire l'accesso globale all'utente corrente in modo coerente.
Quello che mi piacerebbe fare è usare la parola "CurrentUser" nelle associazioni dei dati liberamente per fare riferimento all'utente attualmente connesso. Invece, devo assicurarmi che ogni DataContext mi dia un modo per raggiungere l'oggetto utente corrente. MVVM può adattarsi a questo, ma i modelli di visualizzazione saranno un disastro poiché tutti devono fornire accesso a questi dati globali.
Un esempio in cui cade MVVM
Supponiamo di avere un elenco di utenti. Accanto a ciascun utente, vogliamo visualizzare un pulsante "elimina utente", ma solo se l'utente attualmente connesso è un amministratore. Inoltre, agli utenti non è consentito cancellare se stessi.
Gli oggetti modello non dovrebbero conoscere l'utente attualmente connesso: rappresenteranno solo i record utente nel database, ma in qualche modo l'utente attualmente connesso deve essere esposto a associazioni di dati all'interno delle righe dell'elenco. MVVM impone che dovremmo creare un oggetto viewmodel per ogni riga dell'elenco che compone l'utente attualmente connesso con l'utente rappresentato da quella riga dell'elenco, quindi esporre una proprietà chiamata "DeleteButtonVisibility" o "CanDelete" su quell'oggetto viewmodel (a seconda delle tue sensazioni sui convertitori di associazione).
Questo oggetto assomiglierà moltissimo a un oggetto Utente in molti altri modi: potrebbe essere necessario riflettere tutte le proprietà dell'oggetto modello utente e inoltrare gli aggiornamenti a tali dati quando cambia. Questo sembra davvero icky - ancora una volta, MVVM ti rende un impiegato costringendoti a mantenere questo oggetto user-workalike.
Considera: probabilmente devi anche rappresentare le proprietà del tuo utente in un database, nel modello e nella vista. Se hai un'API tra te e il tuo database, allora è peggio: sono rappresentati nel database, nel server API, nel client API, nel modello e nella vista. Sarei davvero restio ad adottare un modello di progettazione che aggiungesse un altro livello che deve essere toccato ogni volta che una proprietà viene aggiunta o modificata.
Ancora peggio, questo livello si adatta alla complessità dell'interfaccia utente, non alla complessità del modello di dati. Spesso gli stessi dati sono rappresentati in molti punti e nell'interfaccia utente: questo non solo aggiunge un livello, ma aggiunge un livello con una superficie extra!
Come avrebbero potuto essere le cose
Nel caso sopra descritto, vorrei dire:
<Button Visibility="{CurrentUser.IsAdmin && CurrentUser.Id != Id}" ... />
CurrentUser sarebbe esposto a livello globale a tutti gli XAML nella mia app. L'ID si riferirebbe a una proprietà su DataContext per la mia riga di elenco. La visibilità si converte automaticamente da booleano. Qualsiasi aggiornamento a Id, CurrentUser.IsAdmin, CurrentUser o CurrentUser.Id attiverebbe un aggiornamento alla visibilità di questo pulsante. Vai tranquillo.
Invece, WPF / XAML obbliga i suoi utenti a creare un casino completo. Per quanto ne so, alcuni blogger creativi hanno schiaffeggiato un nome su quel casino e quel nome era MVVM. Non farti ingannare: non è della stessa classe dei modelli di design GoF. Questo è un brutto trucco per aggirare un brutto sistema di associazione dei dati.
(Questo approccio viene talvolta definito "Programmazione reattiva funzionale" nel caso in cui si desideri ulteriori letture).
In conclusione
Se devi lavorare in WPF / XAML, non consiglio ancora MVVM.
Volete che il vostro codice sia strutturato come nell'esempio sopra "come avrebbero potuto essere le cose" - il modello esposto direttamente alla vista, con espressioni di associazione dati complesse + coercizioni di valore flessibili. È molto meglio: più leggibile, più scrivibile e più gestibile.
MVVM ti dice di strutturare il tuo codice in un modo più dettagliato, meno gestibile.
Invece di MVVM, crea alcune cose per aiutarti ad approssimare la buona esperienza: sviluppa una convenzione per esporre lo stato globale alla tua interfaccia utente in modo coerente. Costruisci alcuni strumenti con convertitori di rilegatura, MultiBinding, ecc. Che ti consentono di esprimere espressioni di rilegatura più complesse. Costruisci una libreria di convertitori di binding per contribuire a rendere meno dolorosi i casi di coercizione comuni.
Ancora meglio: sostituisci XAML con qualcosa di più espressivo. XAML è un formato XML molto semplice per creare un'istanza di oggetti C #: non sarebbe difficile trovare una variante più espressiva.
Altra mia raccomandazione: non usare toolkit che forzano questo tipo di compromessi. Danneggeranno la qualità del tuo prodotto finale spingendoti verso merda come MVVM invece di concentrarti sul tuo dominio problematico.