En el patrón MVP, ¿debería la Vista crear una instancia de un objeto Modelo basado en el contenido de la IU, o simplemente pasar estos contenidos como parámetros al Presentador?

9

Estoy usando el patrón MVP en una aplicación de Android que estoy desarrollando.

Tengo básicamente 4 elementos:

  1. AddUserView donde se puede agregar un nuevo usuario:
  2. El AddUserPresenter
  3. El UserInfo (el pojo)
  4. UserInfoManager (lógica de negocios y administrador de almacenamiento)

Mi pregunta es:

Cuando presiono el botón "Agregar" en AddUserView, debería obtener el contenido de las vistas de texto, crear una nueva información de usuario y pasarla al presentador. ¿O debería AddUserView obtener los contenidos de textViews y pasarlos al AddUserPresenter, que de hecho creará una instancia de UserInfo y lo pasará a UserInfoManager?

Rômulo.Edu
fuente

Respuestas:

8

De acuerdo con la descripción de MVP de Martin Fowler ( http://martinfowler.com/eaaDev/uiArchs.html )

De la parte de Vista de MVC, Fowler dice:

El primer elemento de Potel es tratar la vista como una estructura de widgets, widgets que corresponden a los controles del modelo de Formularios y controles y eliminar cualquier separación de vista / controlador. La vista de MVP es una estructura de estos widgets. No contiene ningún comportamiento que describa cómo reaccionan los widgets a la interacción del usuario .

(Énfasis en negrita mío)

Luego del presentador:

La reacción activa a los actos del usuario vive en un objeto presentador separado. Los controladores fundamentales para los gestos del usuario todavía existen en los widgets, pero estos controladores simplemente pasan el control al presentador .

El presentador luego decide cómo reaccionar ante el evento. Potel discute esta interacción principalmente en términos de acciones en el modelo, lo que hace mediante un sistema de comandos y selecciones. Una cosa útil para resaltar aquí es el enfoque de empaquetar todas las ediciones al modelo en un comando; esto proporciona una buena base para proporcionar un comportamiento de deshacer / rehacer.

(Nuevamente, el énfasis en negrita es mío)

Por lo tanto, de acuerdo con las pautas de Fowler, su Vista no debe ser responsable de ningún comportamiento en respuesta al evento del botón; que incluye crear una instancia de UserInfo. La responsabilidad de decidir crear un objeto pertenece al método Presentador al que se reenvía el evento de IU.

Sin embargo, uno también podría argumentar que el controlador de eventos del botón Ver tampoco debería ser responsable de pasar el contenido de su textViewtampoco, ya que la Vista simplemente debe reenviar el evento del botón al Presentador y nada más.

Con MVP, es común que la vista implemente una interfaz que el presentador puede usar para recoger datos directamente de la vista (a la vez que se asegura de que el presentador aún sea independiente de la vista en sí). Dado que UserInfo es un POJO simple, puede ser válido para la vista exponer un captador para UserInfo que el presentador puede recoger desde la vista a través de una interfaz.

// The view would implement IView
public interface IView {

    public UserInfo GetUserInfo();
}

// Presenter
public class AddUserPresenter {

    private IView addUserView;

    public void SetView(IView view) {
        addUserView = view
    }

    public void onSomethingClicked() {

        UserInfo userInfo = addUserView.GetUserInfo();
        // etc.
    }
}

¿En qué se diferencia esto de pasar UserInfodirectamente a la vista usando el controlador de eventos? La principal diferencia es que el presentador sigue siendo en última instancia responsable de la lógica que hace UserInfoque se cree un objeto. es decir, el evento llegó al presentador antes de la creación del UserInfo, permitiendo que el presentador tome la decisión.

Imagine un escenario en el que tuviera una lógica de presentador en la que no quisiera que UserInfose creara en función de algún estado dentro de la vista. Por ejemplo, si el usuario no ha marcado una casilla de verificación en la vista, o si tuvo una verificación de validación contra algún campo para agregar a UserInfo que falló, su presentador puede contener una verificación adicional antes de llamar GetUserInfo, es decir

    private boolean IsUsernameValid() {
        String username = addUserView.GetUsername();
        return (username != null && !username.isEmpty());
    }

    public void onSomethingClicked() {            

        if (IsUsernameValid()) {
            UserInfo userInfo = addUserView.GetUserInfo();
            // etc.
        }
    }

Esa lógica permanece dentro del presentador y no necesita agregarse a la vista. Si la vista fuera responsable de llamar GetUserInfo(), también sería responsable de cualquier lógica que rodeara su uso; que es lo que el patrón MVP está tratando de evitar.

Entonces, aunque el método que crea que UserInfopuede existir físicamente en la clase View, nunca se llama desde la clase View, solo desde el Presentador.

Por supuesto, si la creación de los UserInfoextremos requiere verificaciones adicionales contra el contenido de los widgets de entrada del usuario (por ejemplo, conversión de cadenas, validación, etc.), entonces sería mejor exponer captadores individuales para esas cosas para que la validación / conversión de cadenas pueda tomar colocar dentro del presentador, y luego el presentador crea su UserInfo.

En general, su objetivo principal con respecto a la separación entre Presentador / Vista es asegurarse de que nunca necesite escribir lógica en la vista. Si alguna vez necesita agregar una ifdeclaración por algún motivo (incluso si se trata de una ifdeclaración con respecto al estado de una propiedad de widget, marcando un cuadro de texto vacío o un booleano para una casilla de verificación), entonces pertenece al presentador.

Ben Cottrell
fuente
1
Gran respuesta @BenCottrell! Pero tengo otro :) ¿Es una buena práctica nombrar los métodos de presentador como onSomethingClicked(), por lo que cuando el usuario hace clic en "algo", la vista llama presenter.onSomethingClicked()? ¿O mis métodos de presentador deben ser nombrados como las acciones previstas, en mi caso addUser()?
Rômulo.Edu
1
@regmoraes Buena pregunta; y creo que has resaltado un ligero olor en mi código de ejemplo. Por Presentersupuesto, es responsable de la lógica de la interfaz de usuario en lugar de la lógica de dominio, y se adapta específicamente a la View, por lo tanto, los conceptos que deberían existir son conceptos de la interfaz de usuario, por lo que un método llamado onSomethingClicked()es realmente apropiado. En retrospectiva, los nombres que he elegido en mi ejemplo anterior no huelen bien :-).
Ben Cottrell
@BenCottrell En primer lugar muchas gracias por una excelente respuesta. Entiendo, es válido tener este GetUserInfométodo en la vista como mencionaste (se activará desde el presentador) ¿Qué pasa con las posibles ifcondiciones dentro del GetUserInfométodo? ¿Quizás algunos campos de UserInfo se establecerán mediante la reacción del usuario? Un escenario: tal vez el usuario seleccione una casilla de verificación y luego algunos componentes nuevos (tal vez un nuevo EditText) serán visibles para el usuario. Entonces, en ese caso, el GetUserInfométodo tendrá la condición if. En este escenario GetUserInfoes válido todavía?
blackkara
1
@Blackkara Considere tratarlo UserInfocomo un modelo de vista (también conocido como "Ver modelo"): en ese escenario, agregaría el booleanestado de la casilla de verificación y el estado vacío / anulable Stringdel cuadro de texto UserInfo. Incluso podría considerar cambiarle el nombre UserInfoViewModelsi eso ayuda a pensar en términos de que POJO es una clase cuyo único propósito real es permitir que UserInfoPresenterdescubra información sobre el estado Ver.
Ben Cottrell