MVVM e modello di servizio


14

Sto creando un'applicazione WPF usando il modello MVVM. In questo momento, i miei modelli di visualizzazione chiama il livello di servizio per recuperare i modelli (come non è rilevante per il modello di visualizzazione) e convertirli in modelli di visualizzazione. Sto usando l'iniezione del costruttore per passare il servizio richiesto al modello di visualizzazione.

È facilmente testabile e funziona bene con viewmodels con poche dipendenze, ma non appena provo a creare viewModels per modelli complessi, ho un costruttore con MOLTI servizi iniettati (uno per recuperare ogni dipendenza e un elenco di tutti i valori disponibili per associare ad un itemsSource per esempio). Mi chiedo come gestire più servizi come quello e avere ancora un modello di visualizzazione che posso facilmente testare unità.

Sto pensando ad alcune soluzioni:

  1. Creazione di un singleton di servizi (IServices) contenente tutti i servizi disponibili come interfacce. Esempio: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). In questo modo, non ho un costruttore enorme con una tonnellata di parametri di servizi.

  2. Creare una facciata per i servizi utilizzati da viewModel e passare questo oggetto nel ctor del mio viewmodel. Ma poi, dovrò creare una facciata per ciascuno dei miei modelli complessi, e potrebbe essere un po 'troppo ...

Quale pensi sia il modo "giusto" per implementare questo tipo di architettura?


Penso che il modo "giusto" per farlo sia quello di creare un livello separato che chiama i servizi e fa tutto il cast necessario per creare ViewModel. ViewModels non dovrebbe essere responsabile della creazione di se stessi.
Amy Blankenship,

@AmyBlankenship: i modelli di visualizzazione non dovrebbero (o addirittura essere necessariamente in grado di) crearsi, ma inevitabilmente saranno talvolta responsabili della creazione di altri modelli di visualizzazione . Un container IoC con supporto automatico di fabbrica è di grande aiuto qui.
Aaronaught,

"A volte" e "dovrebbero" sono due animali diversi;)
Amy Blankenship,

@AmyBlankenship: stai suggerendo che i modelli di vista non dovrebbero creare altri modelli di vista? È una pillola dura da ingoiare. Posso capire dicendo che i modelli di vista non dovrebbero essere usati newper creare altri modelli di vista, ma penso a qualcosa di semplice come un'applicazione MDI in cui facendo clic su un pulsante o menu "nuovo documento" si aggiungerà una nuova scheda o si aprirà una nuova finestra. La shell / il conduttore deve essere in grado di creare nuove istanze di qualcosa , anche se è nascosto dietro uno o alcuni livelli di riferimento indiretto.
Aaronaught,

Bene, certamente deve avere la capacità di richiedere che una Vista sia fatta da qualche parte. Ma farlo da solo? Non nel mio mondo :). Ma poi di nuovo, nel mondo in cui vivo, chiamiamo VM "Modello di presentazione".
Amy Blankenship,

Risposte:


22

In effetti, entrambe queste soluzioni sono pessime.

Creazione di un singleton di servizi (IServices) contenente tutti i servizi disponibili come interfacce. Esempio: Services.Current.XXXService.Retrieve (), Services.Current.YYYService.Retrieve (). In questo modo, non ho un costruttore enorme con una tonnellata di parametri di servizi.

Questo è essenzialmente il modello di localizzazione di servizio , che è un anti-modello. Se lo fai, non sarai più in grado di capire da cosa dipende effettivamente il modello di vista senza guardare alla sua implementazione privata, il che renderà molto difficile il test o il refactoring.

Creare una facciata per i servizi utilizzati da viewModel e passare questo oggetto nel ctor del mio viewmodel. Ma poi, dovrò creare una facciata per ciascuno dei miei modelli complessi, e potrebbe essere un po 'troppo ...

Questo non è tanto un anti-schema ma è un odore di codice. Fondamentalmente stai creando un oggetto parametro , ma il punto del modello di refactoring PO è quello di gestire i set di parametri che vengono usati frequentemente e in molti luoghi diversi , mentre questo parametro verrebbe usato solo una volta. Come accennato, creerebbe un sacco di codice gonfio senza alcun vantaggio reale e non funzionerebbe bene con molti contenitori IoC.

In effetti, entrambe le strategie di cui sopra trascurano il problema generale, ovvero che l' accoppiamento è troppo elevato tra modelli e servizi di visualizzazione . Nascondere semplicemente queste dipendenze in un localizzatore di servizi o in un oggetto parametro non modifica effettivamente il numero di oggetti da cui dipende il modello di vista.

Pensa a come testeresti uno di questi modelli di vista. Quanto sarà grande il tuo codice di installazione? Quante cose devono essere inizializzate affinché funzioni?

Molte persone che iniziano con MVVM provano a creare modelli di visualizzazione per un intero schermo , che è fondamentalmente l'approccio sbagliato. MVVM è tutto incentrato sulla composizione e uno schermo con molte funzioni dovrebbe essere composto da diversi modelli di vista, ognuno dei quali dipende solo da uno o pochi modelli / servizi interni. Se devono comunicare tra loro, lo fai tramite pub / sub (broker messaggi, bus eventi, ecc.)

Quello che devi effettivamente fare è riformattare i tuoi modelli di vista in modo che abbiano meno dipendenze . Quindi, se è necessario disporre di uno "schermo" aggregato, creare un altro modello di vista per aggregare i modelli di vista più piccoli. Questo modello di vista aggregata non deve fare molto da solo, quindi a sua volta è anche abbastanza facile da capire e testare.

Se lo hai fatto correttamente, dovrebbe essere ovvio solo guardando il codice, perché avrai modelli di vista brevi, concisi, specifici e testabili.


Sì, è quello che probabilmente finirò per fare! Grazie mille, signore.
alfa-alfa,

Bene, ho già pensato che ci avesse già provato, ma non ci sono riuscito. @ alfa-alfa
Euforico

@Euforico: come "non riesci" in questo? Come direbbe Yoda: fare o no, non c'è tentativo.
Aaronaught,

@Aaronaught Ad esempio, ha davvero bisogno di tutti i dati in un unico modello. Forse ha una griglia e colonne diverse provengono da servizi diversi. Non puoi farlo con la composizione.
Euforico

@Euforico: in realtà, puoi risolverlo con la composizione, ma ciò può essere fatto sotto il livello del modello della vista. Si tratta semplicemente di creare le giuste astrazioni. In tal caso, è necessario un solo servizio per gestire la query iniziale per ottenere un elenco di ID e una sequenza / elenco / matrice di "arricchitori" che annotano con le proprie informazioni. Rendi la griglia stessa il suo modello di visualizzazione e hai risolto il problema con effettivamente due dipendenze ed è estremamente facile da testare.
Aaronaught,

1

Potrei scrivere un libro su questo ... in effetti lo sono;)

Prima di tutto, non esiste un modo universalmente "giusto" per fare le cose. Devi prendere in considerazione altri fattori.

È possibile che i tuoi servizi siano troppo precisi. Avvolgere i servizi con le facciate che forniscono l'interfaccia che un Viewmodel specifico o anche un cluster di ViewModel correlati potrebbe utilizzare potrebbe essere una soluzione migliore.

Sarebbe ancora più semplice avvolgere i servizi in un'unica facciata utilizzata da tutti i modelli di visualizzazione. Naturalmente, questa può potenzialmente essere un'interfaccia molto grande con molte funzioni non necessarie per lo scenario medio. Ma direi che non è diverso da un router di messaggi che gestisce tutti i messaggi nel tuo sistema.

In effetti, ciò che ho visto alla fine evolvere in molte architetture è un bus di messaggi costruito attorno a qualcosa come il modello Event Aggregator. Il test è facile perché la tua classe di test registra un ascoltatore con l'EA e genera l'evento appropriato in risposta. Ma questo è uno scenario avanzato che richiede tempo per crescere. Dico di iniziare con la facciata unificante e andare da lì.


Una massiccia facciata di servizio è molto diversa da un broker di messaggi. È quasi all'estremità opposta dello spettro delle dipendenze. Un tratto distintivo di questa architettura è che il mittente non sa nulla del destinatario e potrebbero esserci molti ricevitori (pub / sub o multicast). Forse lo stai confondendo con il "remoting" in stile RPC che sta semplicemente esponendo un servizio tradizionale su un protocollo remoto e il mittente è ancora accoppiato alla ricezione, sia fisicamente (indirizzo dell'endpoint) che logicamente (valore di ritorno).
Aaronaught,

La somiglianza è che la facciata si comporta come un router, il chiamante non sa quale servizio / servizi gestisce la chiamata proprio come un client che invia un messaggio non sa chi gestisce il messaggio.
Michael Brown,

Sì, ma la facciata è quindi un oggetto divino . Ha tutte le dipendenze che hanno i modelli di visualizzazione, probabilmente di più perché sarà condiviso da diversi. In effetti hai eliminato i vantaggi dell'accoppiamento libero per cui hai lavorato così duramente in primo luogo, perché ora, ogni volta che qualcosa tocca la mega-facciata, non hai idea di quale funzionalità dipenda realmente. Immagine che scrive un test unitario per una classe che utilizza la facciata. Si crea un finto per la facciata. Ora, quali metodi prendi in giro? Come appare il tuo codice di installazione?
Aaronaught,

Questo è molto diverso da un broker di messaggi perché anche il broker non sa nulla dell'implementazione dei gestori di messaggi . Usa l'IoC sotto il cofano. La facciata sa tutto sui destinatari perché deve inoltrare le chiamate a loro. Il bus ha zero accoppiamento; la facciata presenta un accoppiamento efferente oscenamente alto. Quasi tutto ciò che cambi, ovunque, influenzerà anche la facciata.
Aaronaught,

Penso che parte della confusione qui - e lo vedo abbastanza - sia proprio il significato di una dipendenza . Se hai una classe che dipende da un'altra classe, ma chiama 4 metodi di quella classe, ha 4 dipendenze, non 1. Mettere tutto dietro una facciata non cambia il numero di dipendenze, le rende solo più difficili da capire .
Aaronaught,

0

Perché non combinare entrambi?

Crea una facciata e inserisci tutti i servizi utilizzati dai tuoi viewmodels. Quindi puoi avere un'unica facciata per tutti i tuoi modelli di visualizzazione senza la brutta parola S.

Oppure puoi usare l'iniezione di proprietà invece dell'iniezione del costruttore. Ma poi, devi assicurarti che vengano iniettati correttamente.


Questa sarebbe una risposta migliore se fornissi un esempio in pseudo-C #.
Robert Harvey,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.