Fat Models, skin ViewModels e Views stupidi, il miglior approccio MVVM?

Attraverso un aiuto generoso su questa domanda , ho messo insieme la seguente struttura MVVM che mostra i cambiamenti di un modello in tempo reale in XAML (data / ora corrente), molto bella.

Un grande vantaggio di questa configurazione è che quando guardi la tua vista in modalità progettazione di Visual Studio o Blend, vedi il tempo che scorre , il che significa che in fase di progettazione hai accesso ai dati in tempo reale dal tuo modello.

Nel processo di ottenere questo lavoro, sono stato sorpreso di vedere la maggior parte del trasferimento di massa dal mio ViewModel nel mio modello , compresa l’implementazione di INotifyPropertyChange. Un altro cambiamento è che non leghiamo più alle proprietà sul ViewModel ma ai metodi .

Quindi attualmente questo è il mio gusto preferito di MVVM:

  1. La vista è stupida:

    • un ObjectDataProvider per ogni object di cui hai bisogno dal tuo modello
    • ogni ObjectDataProvider esegue il mapping su un metodo sul ViewModel (non una proprietà)
    • no x: nome delle proprietà negli elementi XAML
  2. ViewModel è magro:

    • l’unica cosa nel tuo ViewModel sono i metodi a cui la tua vista si lega
  3. Il modello è grasso:

    • il modello implementa INotifyPropertyChanged su ciascuna delle sue proprietà.
    • per ogni metodo sul ViewModel (ad es. GetCurrentCustomer) esiste un metodo Singleton corrispondente nel modello (ad esempio, GetCurrentCustomer).
    • il modello si prende cura di qualsiasi funzionalità di threading in tempo reale come in questo esempio

Domande:

  1. Quelli di voi che hanno implementato MVVM in scenari reali, è questa la struttura di base su cui vi siete stabiliti, e in caso contrario, come varia la vostra?
  2. Come estenderesti questo per includere i comandi indirizzati e gli eventi instradati?

Il seguente codice funzionerà se copi solo XAML e codice nascosto in un nuovo progetto WPF.

XAML:

               

Codice dietro:

 using System.Windows; using System.ComponentModel; using System; using System.Threading; namespace TestBinding99382 { public partial class Window1 : Window { public Window1() { InitializeComponent(); } } //view model public class ShowCustomerViewModel { public Customer GetCurrentCustomer() { return Customer.GetCurrentCustomer(); } } //model public class Customer : INotifyPropertyChanged { private string _firstName; private string _lastName; private DateTime _timeOfMostRecentActivity; private static Customer _currentCustomer; private Timer _timer; public string FirstName { get { return _firstName; } set { _firstName = value; this.RaisePropertyChanged("FirstName"); } } public string LastName { get { return _lastName; } set { _lastName = value; this.RaisePropertyChanged("LastName"); } } public DateTime TimeOfMostRecentActivity { get { return _timeOfMostRecentActivity; } set { _timeOfMostRecentActivity = value; this.RaisePropertyChanged("TimeOfMostRecentActivity"); } } public Customer() { _timer = new Timer(UpdateDateTime, null, 0, 1000); } private void UpdateDateTime(object state) { TimeOfMostRecentActivity = DateTime.Now; } public static Customer GetCurrentCustomer() { if (_currentCustomer == null) { _currentCustomer = new Customer { FirstName = "Jim" , LastName = "Smith" , TimeOfMostRecentActivity = DateTime.Now }; } return _currentCustomer; } //INotifyPropertyChanged implementation public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(string property) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } } } } 

Ecco la mia opinione, per quello che vale:

Non sono davvero d’accordo con l’approccio che suggerisci (tranne che per la vista stupida). Nella vita reale, dovrai spesso utilizzare un modello esistente: potrebbe essere il codice legacy che non hai il tempo (o la volontà) di cambiare, o anche una libreria per la quale non hai il codice. A mio parere, il modello dovrebbe essere completamente inconsapevole del modo in cui verrà visualizzato e dovrebbe essere facilmente utilizzabile in un’applicazione non WPF. Quindi non deve implementare alcuna interfaccia specifica come INotifyPropertyChanged di INotifyCollectionChanged per renderlo utilizzabile in MVVM. Penso che tutta la logica relativa all’interfaccia utente debba risiedere nel ViewModel.

Per quanto riguarda RoutedEvents e RoutedCommands , non sono realmente adatti per l’uso con il pattern MVVM. Di solito cerco di usare il minor RoutedEvents ansible di RoutedCommands e nessun RoutedCommands . Invece, il mio ViewModels espone RelayCommand proprietà RelayCommand che RelayCommand all’interfaccia utente in XAML (vedi questo articolo di Josh Smith per i dettagli su RelayCommand ). Quando ho davvero bisogno di gestire gli eventi per un certo controllo, uso i comportamenti collegati per mappare gli eventi ai comandi ViewModel (dai un’occhiata all’implementazione di Marlon Grech )

Quindi, in sintesi:

  • Vista stupida
  • ViewModel grande e intelligente
  • Qualsiasi modello tu voglia o debba usare

Naturalmente è solo il mio approccio, e potrebbe non essere il migliore, ma mi sento abbastanza a mio agio con esso;)

Sono d’accordo con Thomas. Il mio consiglio a chiunque su WPF architecturing sarebbe:

  • Semplici entity framework POCO senza alcun valore di InotifyPropertyChange, tracciamento stato, BL, ecc.
  • ViewModels semplici e piccoli che notificano le visualizzazioni just-in-time
  • Semplice interfaccia utente riutilizzabile con un sistema di navigazione intelligente che evita complesse gerarchie di dati e complessi ViewModels sottostanti
  • MVVM con View Primo approccio per mantenere semplici le dipendenze
  • Operazioni asincrone con attività o Rx
  • Un tema semplice
  • Nessuna UI complessa e robusta, mantienila semplice, approfitta della composizione UI di WPF e delle capacità di associazione
  • Non esitare a utilizzare il code-behind per generare contenuti dynamicmente (moduli, elenchi ecc.) E risparmiare tempo significativo sulla configurazione dichiarativa degli occhi (si applica alla maggior parte dei casi) – e per me un must nel 2015. Utilizza i metodi di estensione per creare un’API Fluent per quello.