Pensamientos de implementación Modelo-Vista-Presentador

34

Estoy tratando de comprender bien cómo implementar un buen desacoplamiento entre una interfaz de usuario y el modelo, pero tengo problemas para determinar exactamente dónde dividir las líneas.

He estado mirando Model-View-Presenter, pero no estoy seguro de cómo proceder para implementarlo. Por ejemplo, mi Vista tiene múltiples cuadros de diálogo.

  • ¿Debería haber una clase View con instancias de cada uno de los cuadros de diálogo? Entonces, en ese caso, ¿cómo deberían interactuar los diálogos con el presentador? es decir. Si un diálogo individual necesita solicitar datos del Modelo a través del Presentador, ¿cómo debería el diálogo obtener una referencia al Presentador? ¿A través de una referencia a la Vista que se le dio durante la construcción?
  • Estaba pensando que tal vez la vista debería ser una clase estática? Luego, los cuadros de diálogo GetView y obtener el presentador desde allí ...
  • Había estado pensando en configurar el presentador con la propiedad de la vista y el modelo (en lugar de que la vista tenga el presentador y el presentador que tenga el modelo) y que el presentador registre devoluciones de llamada para eventos en la vista, pero eso parece mucho más acoplado (o el idioma dependía, al menos).

Estoy tratando de:

  1. hacer esto lo más desacoplado posible
  2. idealmente, es posible acoplar el presentador / modelo con vistas de otros idiomas (no he hecho un montón de cosas entre idiomas, pero sé que es posible, particularmente cuanto más void(void)puedo seguir, al menos una aplicación C # con un Biblioteca C ++ ...
  3. mantenga el código limpio y simple

Entonces ... ¿alguna sugerencia sobre cómo deben manejarse las interacciones?

trata de atraparlo
fuente
¿Has mirado este artículo ?: en.wikipedia.org/wiki/Model-view-presenter
Bernard
1
Tengo .. Me pareció un poco rápida y de alto nivel, estoy buscando para entender mejor cómo manejar múltiples cuadros de diálogo en un gran proyecto con tan poco como sea posible acoplamiento ..
TryCatch

Respuestas:

37

Bienvenido a una pendiente resbaladiza. En este punto, se ha dado cuenta de que hay una variación interminable de todas las interacciones modelo-vista. MVC, MVP (Taligent, Dolphin, Passive View), MVVM solo por nombrar algunos.

El patrón Model View Presenter, como la mayoría de los patrones arquitectónicos, está abierto a mucha variedad y experimentación. Lo único que todas las variaciones tienen en común es el papel del presentador como "intermediario" entre la vista y el modelo. Los dos más comunes son la Vista pasiva y el Presentador / Controlador supervisor - [ Fowler ]. La Vista pasiva trata la IU como una interfaz muy superficial entre el usuario y el presentador. Contiene muy poca o ninguna lógica, delegando tanta responsabilidad a un presentador. Presentador / controlador supervisorintenta aprovechar el enlace de datos integrado en muchos marcos de IU. La interfaz de usuario maneja la sincronización de datos, pero el presentador / controlador interviene para obtener una lógica más compleja. En cualquier caso, el modelo, la vista y el presentador forman una tríada

Hay muchas maneras de hacer esto. Es muy común ver esto manejado tratando cada diálogo / formulario como una vista diferente. Muchas veces hay una relación 1: 1 entre puntos de vista y presentadores. Esta no es una regla difícil y rápida. Es bastante común que un presentador maneje múltiples vistas relacionadas o viceversa. Todo depende de la complejidad de la vista y la complejidad de la lógica empresarial.

En cuanto a cómo las opiniones y los presentadores obtienen una referencia mutua, esto a veces se llama cableado . Tienes tres opciones:

La vista contiene una referencia al presentador
Un formulario o cuadro de diálogo implementa una vista. El formulario tiene controladores de eventos que delegan a un presentador mediante llamadas de función directas:

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

Como el presentador no tiene una referencia a la vista, la vista debe enviarle datos como argumentos. El presentador puede comunicarse con la vista utilizando eventos / funciones de devolución de llamada que la vista debe escuchar.

El presentador tiene una referencia para ver
En el escenario, la vista expone las propiedades de los datos que muestra al usuario. El presentador escucha los eventos y manipula las propiedades en la vista:

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

Ambos tienen una referencia entre sí formando una dependencia circular.
En realidad, este escenario es más fácil de trabajar que los demás. La vista responde a eventos llamando a métodos en el presentador. El presentador lee / modifica los datos de la vista a través de las propiedades expuestas.

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

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

Hay otros problemas a considerar con los patrones MVP. Orden de creación, vida útil del objeto, donde se realiza el cableado, comunicación entre las tríadas MVP, pero esta respuesta ya ha crecido lo suficiente.

Kenneth Cochran
fuente
1
Esto definitivamente es útil. La comunicación entre las tríadas y la vida es donde actualmente tengo problemas ahora que estoy captando algo de esto.
trycatch
8

Como todos han dicho, hay docenas de opiniones y ninguna de ellas es correcta o incorrecta. Sin entrar en la miríada de patrones y solo centrarnos en MVP, aquí hay algunas sugerencias sobre la implementación.

Mantenlos separados. La vista debe implementar una interfaz que forme el vínculo entre la vista y el presentador. La vista crea un presentador, se inyecta en el presentador y expone los métodos que ofrece para que el presentador interactúe con la vista. La vista es responsable de implementar estos métodos o propiedades de la forma que desee. Generalmente tiene una vista: un presentador, pero en algunos casos puede tener muchas vistas: un presentador (web, wpf, etc.). La clave aquí es que el presentador no sabe nada de las implementaciones de la interfaz de usuario y solo interactúa con la vista a través de la interfaz.

Aquí hay un ejemplo. Primero tenemos una clase de vista con un método simple para mostrar un mensaje al usuario:

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

Ahora aquí está el presentador. Tenga en cuenta que el presentador toma una IView en su constructor.

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

Ahora aquí está la interfaz de usuario real. Esto podría ser una ventana, un diálogo, una página web, etc. No importa. Tenga en cuenta que el constructor de la vista creará el presentador al inyectarse en él.

class View : IView
{
  private Presenter _presenter;

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

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

Al presentador no le importa cómo la vista implementa el método que simplemente hace. Por lo que sabe el presentador, podría estar escribiendo en un archivo de registro y ni siquiera mostrándolo al usuario.

En cualquier caso, el presentador trabaja un poco con el modelo en el back-end y en algún momento quiere informar al usuario sobre lo que está sucediendo. Entonces, ahora tenemos un método en algún lugar del presentador que llama a las vistas del mensaje InformUser.

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

Aquí es donde obtienes tu desacoplamiento. El presentador solo tiene una referencia a una implementación de IView y realmente no le importa cómo se implementa.

Esta también es una implementación deficiente del hombre, ya que tiene una referencia al Presentador en la vista y los objetos se configuran a través de constructores. En una solución más robusta, es probable que desee ver contenedores de inversión de control (IoC) como Windsor, Ninject, etc., que resolverían la implementación de IView para usted en tiempo de ejecución a pedido y, por lo tanto, lo desacoplarían aún más.

Bil Simser
fuente
4

Creo que es importante recordar que el controlador / presentador es donde realmente tiene lugar la acción. El acoplamiento en el controlador es inevitable debido a la necesidad.

El punto central del Controlador es que si realiza un cambio en la Vista, el Modelo no tiene que cambiar y viceversa (si el Modelo cambia, la Vista tampoco tiene que hacerlo) porque el Controlador es lo que traduce Modelar en la vista y volver de nuevo. Pero el Controlador cambiará cuando los cambios en el Modelo o en la Vista lo hagan porque efectivamente tiene que traducir dentro del Controlador cómo se va a ver el Modelo y cómo volver a realizar los cambios en la Vista en el Modo.

El mejor ejemplo que puedo dar es que cuando escribo una aplicación MVC, no solo puedo tener datos en la vista de la GUI, sino que también puedo escribir una rutina que empuja los datos extraídos del Modelo stringa mostrar en el depurador (y por extensión en un archivo de texto sin formato). Si puedo tomar datos del Modelo y traducirlos libremente en texto sin cambiar la Vista o el Modelo y solo el Controlador, entonces estoy en el camino correcto.

Dicho esto, tendrá que tener referencias entre los diferentes componentes para que todo funcione. El Controlador necesita saber acerca de la Vista para enviar datos, la Vista necesita saber sobre el Controlador para decirle cuándo se ha realizado un cambio (como cuando el Usuario hace clic en "Guardar" o "Nuevo ..."). El controlador necesita saber sobre el modelo para extraer los datos, pero yo diría que el modelo no debería saber nada más.

Advertencia: vengo de un fondo totalmente Mac, Objective-C, Cocoa que realmente te empuja al paradigma MVC, lo quieras o no.

Philip Regan
fuente
Este es definitivamente mi objetivo. Mi principal problema es cómo configurar la Vista: si debería ser una clase con una instancia de cada diálogo y luego usar View.Getters que llama a Dialog.Getters, o si el Presentador debería poder llamar a Dialog.Getters directamente ( esto parece demasiado ajustado, ¿entonces probablemente no?)
trycatch
Creo que el presentador / controlador debe ser totalmente responsable de las vistas, por lo que este último. De nuevo, es probable que se produzca algún acoplamiento, pero al menos si la dirección de la responsabilidad es clara, el mantenimiento debería ser más fácil a largo plazo.
Philip Regan
2
Ciertamente estoy de acuerdo en que el P / C debería ser responsable de la Vista, pero pensé que parte de lo que se suponía que debía hacer que MVP fuera poderoso era la capacidad de extraer toda la biblioteca de la interfaz de usuario y enchufar una nueva y un poco de masaje (dllimporting y otras cosas) poder ejecutar otro en su lugar. ¿No sería esto más difícil con el Controlador / Presentador accediendo directamente a los cuadros de diálogo? Desde luego, no estoy tratando de discutir, simplemente entender mejor :)
TryCatch
Creo que el poder real proviene de dos direcciones: la primera es que la Vista y el Modelo no tienen nada que ver con otras, y la segunda que la mayoría del trabajo de desarrollo, el motor de la aplicación, se realiza de forma ordenada unidad, el controlador. Pero es inevitable que ocurra un poco de responsabilidad. Al menos la mayoría del intercambio de interfaces se realizará en el Controlador y cualquier enlace desde la Vista sería mínimo. Como otros han dicho, se espera y permite cierta sangría de lógica. MVC no es una bala mágica.
Philip Regan
El punto importante para el desacoplamiento es que el presentador SOLO accede a la vista a través de interfaces bien definidas (biblioteca de UI independiente), por lo que así es como la biblioteca de UI puede reemplazarse por otra (otra que implementará la misma interfaz para formulario / ventana / diálogo / página / control / lo que sea)
Marcel Toth
2

En general, desea que su modelo encapsule todas las interacciones con ese modelo. Por ejemplo, sus acciones CRUD (Crear, Leer, Actualizar, Eliminar) son parte del modelo. Lo mismo vale para cálculos especiales. Hay un par de buenas razones para esto:

  • Es más fácil automatizar sus pruebas para este código
  • Mantiene todas esas cosas importantes en un solo lugar

En su controlador (aplicación MVC), todo lo que está haciendo es recopilar los modelos que necesita usar en su vista y llamar a las funciones apropiadas en el modelo. Cualquier cambio en el estado del modelo ocurre en esta capa.

Su vista simplemente muestra los modelos que preparó. Esencialmente, la vista solo lee el modelo y ajusta su salida en consecuencia.

Mapeando el principio general a clases reales

Recuerda que tus cuadros de diálogo son vistas. Si ya tiene una clase de diálogo, no hay razón para crear otra clase "Ver". La capa Presentador esencialmente vincula el modelo a los controles en la Vista. La lógica empresarial y todos los datos importantes se almacenan en el modelo.

Berin Loritsch
fuente