Pensieri di implementazione di Model-View-Presenter


34

Sto cercando di capire bene come implementare un buon disaccoppiamento tra un'interfaccia utente e il modello, ma ho problemi a capire esattamente dove dividere le linee.

Ho esaminato Model-View-Presenter, ma non sono sicuro di come implementarlo. Ad esempio, la mia vista ha più finestre di dialogo.

  • Dovrebbe esserci una classe View con istanze di ciascuna finestra di dialogo? Quindi, in quel caso, come dovrebbero interagire i dialoghi con il Presentatore? vale a dire. se una singola finestra di dialogo deve richiedere i dati dal modello tramite il Presenter, in che modo la finestra di dialogo dovrebbe ottenere un riferimento al Presenter? Tramite un riferimento alla vista fornita durante la costruzione?
  • Stavo pensando che forse la vista dovrebbe essere una classe statica? Quindi le finestre di dialogo GetView e ottieni il Presenter da lì ...
  • Stavo pensando di configurarlo come Presenter con la proprietà di View and Model (in contrapposizione a View che ha Presenter e Presenter con Model) e Presenter che registrava callback per eventi nella View, ma questo fa sembrare molto più accoppiati (o almeno una lingua dipende).

Sto provando a:

  1. rendere questo il più possibile disaccoppiato
  2. rendere idealmente possibile accoppiare il presentatore / modello con viste di altre lingue (non ho fatto un sacco di cose inter-lingua, ma so che è possibile, in particolare più void(void)posso attenermi, almeno un'app C # con un Libreria C ++ ...
  3. mantenere il codice pulito e semplice

Quindi ... qualche suggerimento su come gestire le interazioni?


Hai visto questo articolo ?: en.wikipedia.org/wiki/Model-view-presenter
Bernard

1
Ho .. L'ho trovato un po 'veloce e di alto livello, sto cercando di capire meglio come gestire più dialoghi in un grande progetto con il minor accoppiamento possibile ..
Trycatch

Risposte:


37

Benvenuti su una pista scivolosa. A questo punto ti sei reso conto che esiste una variazione infinita di tutte le interazioni vista modello. MVC, MVP (Taligent, Dolphin, Passive View), MVVM solo per citarne alcuni.

Il modello Model View Presenter, come la maggior parte dei modelli architettonici, è aperto a molta varietà e sperimentazione. L'unica cosa che tutte le variazioni hanno in comune è il ruolo del presentatore come "intermediario" tra la vista e il modello. I due più comuni sono la vista passiva e il supervisore presentatore / controller - [ Fowler ]. Passive View considera l'interfaccia utente come un'interfaccia molto superficiale tra l'utente e il presentatore. Contiene pochissima o nessuna logica, delegando la stessa responsabilità a un relatore. Supervisore del presentatore / controllercerca di sfruttare l'associazione dei dati integrata in molti framework dell'interfaccia utente. L'interfaccia utente gestisce la sincronizzazione dei dati ma il presentatore / controller interviene per una logica più complessa. In entrambi i casi il modello, la vista e il presentatore formano una triade

Ci sono molti modi per farlo. È molto comune vederlo gestito trattando ogni finestra di dialogo / modulo come una vista diversa. Molte volte esiste una relazione 1: 1 tra visualizzazioni e presentatori. Questa non è una regola dura e veloce. È abbastanza comune che un relatore gestisca più viste correlate o viceversa. Tutto dipende dalla complessità della vista e dalla complessità della logica aziendale.

Per quanto riguarda il modo in cui le visualizzazioni e i presentatori si scambiano un riferimento, a volte viene chiamato cablaggio . Hai tre scelte:

La vista contiene un riferimento al presentatore
Una maschera o una finestra di dialogo implementa una vista. Il modulo ha gestori di eventi che eseguono il delgate a un relatore utilizzando chiamate di funzione dirette:

MyForm.SomeEvent(Sender)
{
  Presenter.DoSomething(Sender.Data);
}

Poiché il relatore non ha un riferimento alla vista, la vista deve inviargli i dati come argomenti. Il relatore può comunicare di nuovo alla vista usando le funzioni eventi / callback che la vista deve ascoltare.

Il relatore contiene un riferimento da visualizzare
Nello scenario la vista espone le proprietà per i dati che mostra all'utente. Il relatore ascolta gli eventi e manipola le proprietà nella vista:

Presenter.SomeEvent(Sender)
{
  DomainObject.DoSomething(View.SomeProperty);
  View.SomeOtherProperty = DomainObject.SomeData;
}

Entrambi hanno un riferimento reciproco formando una dipendenza circolare
Questo scenario è in realtà più facile da lavorare rispetto agli altri. La vista risponde agli eventi chiamando i metodi nel presentatore. Il relatore legge / modifica i dati dalla vista attraverso le proprietà esposte.

View.SomeEvent(Sender)
{
  Presenter.DoSomething();
}

Presenter.DoSomething()
{
  View.SomeProperty = DomainObject.Calc(View.SomeProperty);
}

Vi sono altri problemi da considerare con i modelli MVP. Ordine di creazione, durata dell'oggetto, luogo in cui avviene il cablaggio, comunicazione tra le triadi MVP ma questa risposta è già cresciuta abbastanza a lungo.


1
Questo è sicuramente utile. La comunicazione tra le triadi e la vita è il punto in cui attualmente ho problemi ora che ne sto cogliendo un po '.
Trycatch il

8

Come tutti hanno detto, ci sono dozzine di opinioni e nessuna di esse è giusta o sbagliata. Senza entrare nella miriade di modelli e concentrarsi solo su MVP ecco alcuni suggerimenti sull'implementazione.

Tienili separati. La vista dovrebbe implementare un'interfaccia che costituisce il legame tra la vista e il presentatore. La vista crea un presentatore e si inietta nel presentatore ed espone i metodi che offre al relatore per interagire con la vista. La vista è responsabile dell'implementazione di questi metodi o proprietà nel modo desiderato. Generalmente hai una vista: un relatore ma in alcuni casi puoi avere molte viste: un relatore (web, wpf, ecc.). La chiave qui è che il presentatore non sa nulla delle implementazioni dell'interfaccia utente e interagisce solo con la vista attraverso l'interfaccia.

Ecco un esempio Innanzitutto abbiamo una classe di visualizzazione con un metodo semplice per visualizzare un messaggio per l'utente:

interface IView
{
  public void InformUser(string message);
}

Ora ecco il presentatore. Nota che il presentatore inserisce un IView nel suo costruttore.

class Presenter
{
  private IView _view;
  public Presenter(IView view)
  {
    _view = view;
  }
}

Ora ecco l'interfaccia utente reale. Potrebbe trattarsi di una finestra, una finestra di dialogo, una pagina Web, ecc. Non importa. Nota che il costruttore per la vista creerà il presentatore iniettandosi in esso.

class View : IView
{
  private Presenter _presenter;

  public View()
  {
    _presenter = new Presenter(this);
  }

  public void InformUser(string message)
  {
    MessageBox.Show(message);
  }
}

Il presentatore non si preoccupa di come la vista implementa il metodo che fa. Per quanto ne sa il presentatore, potrebbe scrivere in un file di registro e non mostrarlo nemmeno all'utente.

In ogni caso, il presentatore lavora un po 'con il modello sul back-end e ad un certo punto vuole informare l'utente su ciò che sta accadendo. Quindi ora abbiamo un metodo da qualche parte nel presentatore che chiama il messaggio InformUser delle viste.

class Presenter
{
  public void DoSomething()
  {
    _view.InformUser("Starting model processing...");
  }
}

Qui è dove ottieni il tuo disaccoppiamento. Il presentatore ha solo un riferimento a un'implementazione di IView e non gli importa davvero come sia implementata.

Anche questa è un'implementazione di tipo scadente poiché nella vista è presente un riferimento al Presenter e gli oggetti vengono impostati tramite i costruttori. In una soluzione più robusta vorrai probabilmente esaminare i contenitori di inversione di controllo (IoC) come Windsor, Ninject, ecc. Che risolverebbero l'implementazione di IView per te in fase di esecuzione su richiesta e rendendola ancora più disaccoppiata.


4

Penso che sia importante ricordare che il Controller / Presenter è il luogo in cui si svolge davvero l'azione. L'accoppiamento nel controller è inevitabile per necessità.

Il punto centrale del controller è che se si apporta una modifica alla vista, il modello non deve cambiare e viceversa (se il modello cambia la vista non deve neanche) perché il controller è ciò che traduce il Modella nella vista e viceversa. Ma il Controller cambierà quando cambiano il Modello o la Vista perché è necessario tradurre in modo efficace all'interno del Controller come il Modello deve vedere come riportare le modifiche nella Vista nella Modalità.

L'esempio migliore che posso fare è che quando scrivo un'app MVC, non solo posso avere dati nella vista GUI, ma posso anche scrivere una routine che spinge i dati estratti dal Modello in un stringda mostrare nel debugger (e per estensione in un file di testo semplice). Se riesco a prendere i dati del modello e tradurli liberamente in testo senza modificare la vista o il modello e solo il controller, allora sono sulla strada giusta.

Detto questo, dovrai avere riferimenti tra i diversi componenti per farlo funzionare. Il controller ha bisogno di conoscere la vista per inviare i dati, la vista deve sapere del controller per dire quando è stata apportata una modifica (come quando l'utente fa clic su "Salva" o "Nuovo ..."). Il controller ha bisogno di conoscere il modello per estrarre i dati, ma direi che il modello non dovrebbe sapere nient'altro.

Avvertenza: vengo da un background totalmente Mac, Objective-C, Cocoa che ti spinge davvero nel paradigma MVC, che tu lo voglia o no.


Questo è sicuramente il mio obiettivo. Il mio problema principale è come impostare la vista: se dovrebbe essere una classe con un'istanza di ciascuna finestra di dialogo e quindi utilizzare View.Getters che chiama Dialog.Getters o se il Presenter dovrebbe essere in grado di chiamare direttamente Dialog.Getters ( questo sembra accoppiato troppo strettamente, quindi probabilmente no?)
Trycatch il

Penso che il Presenter / Controller dovrebbe essere pienamente responsabile dei punti di vista, quindi quest'ultimo. Ancora una volta, è inevitabile che avvenga un certo accoppiamento, ma almeno se la direzione della responsabilità è chiara, la manutenzione dovrebbe essere più semplice nel lungo periodo.
Philip Regan,

2
Sono certamente d'accordo sul fatto che il P / C dovrebbe essere responsabile della vista, ma ho pensato che parte di ciò che doveva rendere potente MVP era la capacità di estrarre l'intera libreria dell'interfaccia utente e collegarne una nuova e con un po 'di massaggio (dllimporting e quant'altro) essere in grado di eseguirne un altro al suo posto. Non sarebbe più difficile se il Controller / Presenter accedesse direttamente alle finestre di dialogo? Certamente non sto cercando di discutere, solo di capire ulteriormente :)
Trycatch

Penso che il vero potere provenga da due direzioni: la prima è che View e Model non hanno nulla a che fare con le altre, e la seconda che la maggior parte del lavoro di sviluppo, il motore dell'app, è fatto in modo ordinato unità, il controller. Ma è inevitabile che si verifichi un sanguinamento di responsabilità. Almeno la maggior parte dello scambio di interfacce verrà effettuata nel controller e qualsiasi collegamento dalla vista sarebbe minimo. Come altri hanno già detto, è previsto e consentito un sanguinamento della logica. MVC non è un proiettile magico.
Philip Regan,

Il punto importante per il disaccoppiamento è che il relatore accede SOLO alla vista attraverso interfacce ben definite (indipendentemente dalla libreria UI), in questo modo la libreria UI può essere sostituita con un'altra (un'altra che implementerà la stessa interfaccia per form / window / dialog / page / control / qualunque)
Marcel Toth,

2

In generale, vuoi che il tuo modello incapsuli tutte le interazioni con quel modello. Ad esempio, le tue azioni CRUD (Crea, Leggi, Aggiorna, Elimina) fanno tutte parte del modello. Lo stesso vale per calcoli speciali. Ci sono un paio di buoni motivi per questo:

  • È più semplice automatizzare i test per questo codice
  • Mantiene tutte quelle cose importanti in un unico posto

Nel controller (app MVC), tutto ciò che stai facendo è raccogliere i modelli che devi utilizzare nella vista e chiamare le funzioni appropriate sul modello. Qualsiasi modifica allo stato del modello avviene in questo livello.

La tua vista mostra semplicemente i modelli che hai preparato. In sostanza, la vista legge solo il modello e regola di conseguenza il suo output.

Mappatura del principio generale su classi reali

Ricorda che le tue finestre di dialogo sono viste. Se hai già una classe di dialogo, non c'è motivo di creare un'altra classe "Visualizza". Il livello Presenter lega essenzialmente il modello ai controlli nella vista. La logica aziendale e tutti i dati importanti sono memorizzati nel modello.

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.