MVP Explicado a las Ovejas

Imagen de título

El patrón arquitectónico MVP, Model View Presenter (Modelo Vista Presentador) lleva en auge desde hace unos años hasta la fecha y el motivo es porque el MVP es un perfecto ejemplo de arquitectura limpia que garantiza el desacoplamiento de las diferentes capas que componen una aplicación.

Índice

Arquitectura limpia

La arquitectura limpia es aquella en la que todas las capas que componen el software son independientes unas de otras, de manera que, a la hora de ampliar las funcionalidades de un proyecto o cambiar un componente por otro, no haya conflicto y este cambio se realice con el menor coste de tiempo posible.

Para verlo de una manera simple, en un mecanismo de engranajes, si uno de los engranajes está defectuoso, solo tenemos que cambiarlo por otro y al resto de engranajes les da igual que en lugar de uno haya otro, no tienen que modificar su comportamiento para funcionar. Esto es un mecanismo bien desacoplado. Pues esto es lo que se pretende hacer con la arquitectura limpia y lo conseguimos de una manera muy adecuada con el MVP.

MVP

Equiparando el MVP con un sistema de engranajes, las distintas capas que componen una aplicación son los engranajes. Estas capas deben ser tan independientes que un cambio en una de ellas no suponga tener que cambiar las demás.

Estamos seguros de que habrás visto este gráfico de las capas de un software montones de veces:

Diagrama de arquitectura limpia

Arquitectura limpia

Permítenos explicártelo:

  • Cada círculo concéntrico es una capa de la arquitectura del software.
    • Entidades: son las clases de Java de tipo modelo. Por ejemplo, en una aplicación de un banco tendríamos la clase Cuenta, la clase Hipoteca, la clase Préstamo, la clase Transacción, etc.
    • Caso de uso: (también son llamados interactors) son la lógica de negocio de la aplicación, la que manipula la información. En la aplicación del banco tendríamos los casos de uso “consultar saldo”, “sacar dinero”, “conceder préstamo”, etc., que se encargarían de realizar la lógica de estas operaciones; solo la lógica.
    • IU: interfaz de usuario. Esta capa está compuesta únicamente por las vistas, en el caso de Android son las activities, los fragments… y todo su contenido. Su cometido solo es presentar la información, pero no realizar ninguna función lógica.
    • Presentador: esta es la capa que conecta la interfaz gráfica con los casos de uso. Su trabajo consiste en recibir peticiones de información de la interfaz de usuario, obtenerla mediante los casos de uso y entregársela.
  • La regla de dependencia indica que las capas interiores no deben conocer a las capas exteriores. Es decir:
    • Una entidad no debe conocer a los casos de uso. Los casos de uso usan las entidades, pero las entidades no saben nada de los casos de uso. De esta manera, si cambia el caso de uso, las entidades nunca lo sabrán y no se verán afectadas.
    • De igual manera, los casos de uso son invocados por los presentadores pero no deben saber qué es un presentador. De esta manera, si cambia un presentador, el caso de uso nunca lo sabrá y no le afectará.
    • Un presentador llama a un caso de uso u otro dependiendo de lo que le solicite la vista, pero no debe saber si la vista es una activity, un fragment o incluso un dialog. Solo debe saber que hay una vista que le solicita cosas y a la que tiene que devolverle cosas (o no). De esta manera, si a un presentador se le cambia la vista, el presentador no se ve afectado.
    • Una vista tiene un presentador pero le da igual lo que el presentador haga. Solo debe saber que tiene un presentador al que puede pedirle la información que necesita.

Responsabilidades en Android

Vamos a ser más concretos con la responsabilidad de cada capa (tal vez te convenga leer esto tras ver los ejemplos prácticos):

  • Entidades: modelan los objetos de negocio. Solo deben encargarse de sí mismos, de sus funcionalidades y de exponer los métodos necesarios para el acceso y asignación de sus campos.
  • Casos de uso: definen de forma modular cada funcionalidad de la aplicación. Por ejemplo, el caso de uso “Buscar” de un buscador. Debe encargarse de, recibir un término de búsqueda y devolver una cantidad de resultados determinados a quien los solicite, pero no de mostrarlos.
  • Presentadores: establecen la comunicación entre IU y casos de uso y gestionan lo que debe hacer la vista. Ante cualquier cosa que suceda en la vista, debe ser informado para decidir qué hay que hacer. Según algunos desarrolladores, no debe contener nada de código de java de Android relativo a las vistas. No debe saber siquiera lo que es una View. Otros por el contrario, añaden a los presentadores métodos nombrados como los métodos del ciclo de vida de la vista para gestionar qué debe hacer la vista en cada uno. Personalmente, prefiero el primer enfoque; dejemos a la vista lo que es de la vista. Cada vista tiene un presentador y cada presentador una vista, aunque hay veces un mismo presentador sirve para varias vistas porque estas hacen lo mismo.
  • Vista: se encarga solo de las cosas de vista: inyectar vistas, asignar listeners, detectar el botón que ha sido pulsado, mostrar u ocultar vistas, recoger el contenido introducido en los campos de texto, establecer el texto que va en los TextView, navegar de una activity a otra, cambiar un fragment por otro, mostrar un dialog… Todo lo que tenga que ver con clases que hereden de View.

Caso práctico simple

A continuación te presentamos una aplicación simple que implementa el MVP de forma rudimentaria. Su único objetivo es el de mostrar un texto cuando se pulse un botón. Puedes descargar el proyecto aquí. En ella puedes observar el esqueleto básico del MVP, donde se distinguen tres tipos de clases:

  • La vista (Activity, Fragment, etc.), que es la interfaz de usuario.
  • El presentador, que es el que llamará a los casos de uso y entregará a la vista todo lo que necesita.
  • Y las interfaces Presentador y Vista, que abstraerán a la vista de su presentador y al presentador de su vista, de manera que ambos estén desacoplados. Estas se aunarán en una clase llamada Contrato.

Implementación

Vamos a proceder paso a implementar el MVP. Este es el diagrama de clases.

Diagrama de clases MVP

Diagrama de clases MVP

Contrato

La clase Contrato contiene dos interfaces, la interfaz Presentador, la cual tendrá métodos a los que llamará la vista para solicitar información, y la interfaz Vista, la cual contendrá los métodos a los que llamará el presentador para solicitar a la vista que haga algo, ya sea mostrar información, mostrar u ocultar elementos…

En primer lugar necesitaremos que la vista solicite a su presentador un texto, así que crearemos el método solicitarTexto() en la interfaz Presentador. Y en segundo lugar necesitaremos que el presentador pueda ordenar a la vista que muestre dicho texto, por lo que crearemos en la interfaz Vista el método mostrarTexto().

 

 

Vista

Nuestra vista será una activity con un botón y nada más.

Implementará la interfaz Contrato.Vista y tendrá:

  • Un objeto del tipo Presentador al que solicitarle las cosas.
  • Un método para obtener dicho presentador.
  • Un listener para que, cuando se pulse el botón, se solicite el texto al presentador.
  • Un método para mostrar el texto, obligado por la interfaz.

Para lograr que la vista no tenga que conocer cuál es su presentador, utilizaremos el inyector de dependencias Dagger (no vamos a explicar aquí cómo utilizarlo, pero puedes ver la clase Module en el proyecto, descárgatelo más arriba). En el método onCreate() le pasaremos la vista al presentador para que este pueda llamar a sus métodos.

 

Presentador

El presentador será una clase que implemente la interfaz Contrato.Presentador y tendrá:

  • Un objeto de tipo Vista.
  • Un método para obtener la vista a la que pertenece y así poder llamar a sus métodos.
  • Un método en el que recibirá llamadas de la vista solicitándole un texto, obligado por la interfaz.

Ahora ya tenemos el flujo completo: al hacer clic en el botón, la vista llama a su presentador para solicitar un texto y el presentador obtiene el texto que su vista ha solicitado y llama a uno de los métodos de esta para que lo muestre.

La vista no conoce a su presentador y el presentador no conoce a su vista gracias a las interfaces. La vista solamente se comunica con su presentador para solicitar información y el presentador es el único que se comunica con la vista. Perfecto.

Si te fijas, habría sido más fácil si el método solicitarTexto() retornara el texto a la vista directamente en lugar de tener que hacer una llamada a un método de la vista, ¿verdad? Pues esto no sería correcto. ¿Por qué? Porque de hacerlo así, estaríamos dando a la vista más responsabilidades de las debidas. Recuerda que cada componente solo debe encargarse de lo suyo; solo así logramos un desacoplamiento efectivo. A la vista solo le compete mostrar la información, pero no decidir si debe mostrarse o no, ni obtenerla, ni decidir qué se muestra o qué no.

Caso práctico con casos de uso

Para poner en práctica la arquitectura MVP con casos de uso, realizaremos una aplicación de una calculadora que realice las cuatro operaciones básicas. Puedes descargar el proyecto aquí. Los casos de uso se inyectan en el presentador y este el encargado de llamarlos, utilizando para ello un programador de tareas o un gestor como JobManager. Aquí lo haremos a lo simple.

Capas

Diferenciaremos primero todas las capas de la arquitectura limpia en la aplicación antes de comenzar:

  • IU: consistirá en una activity con un campo de texto para el resultado, dos campos para los operandos y cuatro botones: uno para cada operación.
  • Presentadores: el presentador de la activity, que se encargará de llamar a las funcionalidades de la calculadora.
  • Casos de uso: las operaciones que realizará la calculadora. Habrá cuatro, uno por cada operación.
  • Entidades: habrá dos entidades, la clase Operando, que únicamente contendrá un campo valor, y la clase Resultado, que también contendrá únicamente un campo valor. A fin de cuentas, ¿para qué necesitamos más?

Así pues, este será el esquema resultante de nuestro proyecto:

Estructura de carpetas del MVP

Estructura del proyecto

Entidades

Estas son las clases Operando y Resultado:

Como puedes ver, no hay mayor dificultad. La aplicación permitirá trabajar solo con números enteros para evitar tener que estar haciendo más comprobaciones. Aunque son iguales, se han creado dos clases distintas porque tiene más sentido: el resultado de una operación no es un operando, es un resultado. En una aplicación real, tendrían diferentes responsabilidades y comportamientos.

Casos de uso

Para ser más prácticos, utilizaremos una interfaz Operación y haremos que todos los casos de uso la implementen. MVP e interfaces están destinados ambos al desacoplamiento de los módulos. Es muy buena idea usarlos juntos.

Los casos de uso tienen que ser inyectados, por lo que debemos dejar un constructor vacío para hacer inyectables los casos de uso. El método init() hará las veces de constructor. Haremos un caso de uso para cada operación.

Una vez visto un caso de uso, ya te imaginarás que los otros tres son iguales pero cambiando el tipo de operación que se realiza en el método calcular(), así que no los vamos a poner aquí. En el de dividir, para evitar que la aplicación se detenga, haremos que el resultado sea cero si se intenta dividir entre cero.

Puedes ver la clase del módulo de Dagger en el proyecto descargable.

Contrato

Como antes, el MVP requiere que creemos un contrato con las interfaces Vista y Presentador. La vista tendrá que poder enviar los operandos al presentador, mostrar un error o mostrar el resultado. El presentador, por su parte, tendrá que poder obtener su vista, establecer el tipo de operación que hay que realizar y calcular el resultado:

Vista

La vista implementará Contrato.Vista y contendrá los botones y los campos de texto. Asignará un listener a cada botón de operación para informar al presentador de cuál es la operación seleccionada y asignará un listener al botón para obtener el resultado de la operación. También tendrá que encargarse de obtener el contenido de los EditTexts de los operandos y pasárselos al presentador cuando este se los solicite. Y por último deberá tener un método para informar del resultado, de los errores y del tipo de operación seleccionada.

Presentador

El presentador tendrá la función de hacer que se realicen las operaciones y devolver el resultado calculado. Obtendrá de la vista los operandos, creará el caso de uso necesario para realizar la operación indicada y devolverá el resultado a la vista y, en caso de fallo, devolverá un error. También implementará el método para enviar a la vista el carácter que tiene que mostrar según el tipo de operación seleccionada.

Y listo, ya estamos preparados. Cada capa hace solo aquello que le compete. La vista recibe y muestra información del y para el usuario, el presentador se encarga de gestionar qué es lo que la vista debe hacer y los casos de uso que se deben ejecutar, y los casos de uso hacen el trabajo duro.

Tal vez te resulte extraño el método obtenerCaracterOperacion() porque podría ir fácilmente declarado en la propia activity pero esto, igual que en el ejemplo sencillo, sería un error. A la vista no le compete decidir qué tiene que mostrar; solo mostrar. La lógica para determinar qué carácter se muestra no es lógica perteneciente a la vista: la operación podría ser más complicada, podría conllevar que no se muestre nada si ya se está mostrando algo, que si hay un determinado tipo de operación seleccionada no se pueda seleccionar otra, podría ampliarse en un futuro con más caracteres para más operaciones, podría decidir, bajo determinadas condiciones, mostrar un símbolo diferente al pulsar un botón, como cuando se mantiene pulsada la tecla Shift… Este comportamiento debería estar desacoplado con miras a la escalabilidad de la aplicación.

Y en definitiva, esto es el MVP. Cada uno a lo suyo.

1 Comentario
  1. Miguel 1 mes

    Muchas gracias por compartir esta información. Al fin me quedó claro el uso del MVP.
    Saludos desde Perú.

Contesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

*

©2017 Codictados Comunidad libre para el aprendizaje de codigo Online

o

Inicia Sesión con tu Usuario y Contraseña

o    

¿Olvidó sus datos?