-
Introducción a Composite UI Application Block (CAB) IV
2 CommentsModel-View-Presenter
En general los formularios siempre están formados por varios controles, manejan eventos y contienen lógica para responder a estos eventos. Si se escribe este código dentro de la clase del formulario, se hace demasiado compleja y difícil de probar. Una posible solución a este problema es separar las responsabilidades de mostrar los aspectos visuales y el manejo de los eventos en diferentes clases. Una clase representará a la vista, la cual controlará el formulario, y la otra, el presenter, se encargará del manejo de los eventos y de actualizar correctamente a la vista.
El patrón Model-View-Presenter (MVP) separa el modelo del dominio, la presentación y las acciones basadas en la interacción con el usuario en tres clases separadas. La vista le delega a su presenter toda la responsabilidad del manejo de los eventos del usuario. El presenter se encarga de actualizar el modelo cuando surge un evento en la vista, pero también es responsable de actualizar a la vista cuando el modelo le indica que ha cambiado. El modelo no conoce la existencia del presenter. Por lo tanto, si el modelo cambia por acción de algún otro componente que no sea el presenter, debe disparar un evento para que el presenter se entere.
A la hora de implementar este patrón, se identifican los siguientes componentes:
· IView: es la interfaz con la que el presenter se comunica con la vista.
· View: vista que implementa la interfaz IView y se encarga de manejar los aspectos visuales. Mantiene una referencia a su presenter al cual le delega la responsabilidad del manejo de los eventos.
· Presenter: contiene la lógica para responder a los eventos y manipula el estado de la vista mediante una referencia a la interfaz IView. El presenter utiliza el modelo para saber cómo responder a los eventos.
· Model: Esta compuesto por los objetos que conocen y manejan los datos dentro de la aplicación. Por ejemplo, pueden ser las clases que conforman el modelo del negocio (business entities).
La Figura1 ilustra la lógica del patrón Model-View-Presenter (MVP).
Figura 1
Vista lógica del patrón MVP.MVP aplicado en CAB
CAB provee una infraestructura que permite aplicar fácilmente el patrón MVP para mejorar el diseño de nuestras aplicaciones. Johnny Halife escribió hace un tiempo el siguiente post: How CAB and TDD helps doing better designs, donde explica cómo CAB permite mejorar nuestros diseños.
A continuación se mostrarán implementaciones de ejemplo de una vista con su presenter utilizando las herramientas proporcionas por Composite UI Application Block.
Vista
Gracias a la inyección de dependenicias (Dependency Injection) la vista puede inyectar una nueva instancia de su presenter cuando esta se crea. El siguiente código muestra una implementación de una vista usando el patrón MVP:
[C#]
[SmartPart]
public partial class View : UserControl, IView
{
private Presenter _presenter = null;
[CreateNew]
public Presenter Presenter
{
set
{
_presenter = value;
_presenter.View = this;
}
}
public View()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
_presenter.OnViewReady();
base.OnLoad(e);
}
#region IView Members
// TODO: Implementar los metodos de la interfaz IView.
#endregion
// TODO: Agregar metodos que controlen los controles de
// la vista.
}
[Visual Basic]
<SmartPart()> _
Partial Public Class View
Inherits UserControl
Implements IView
Private _presenter As Presenter = Nothing
_
Public WriteOnly Property Presenter() As Presenter
Set(ByVal value As Presenter)
_presenter = value
_presenter.View = Me
End Set
End Property
Public Sub New()
InitializeComponent()
End Sub
Protected Overrides Sub OnLoad(ByVal e As EventArgs)
_presenter.OnViewReady()
MyBase.OnLoad(e)
End Sub
‘ TODO: Implementar los metodos de la interfaz IView.
‘ TODO: Agregar metodos que controlen los controles de
‘ la vista.
End Class
Analizando el código anterior, lo primero que se observa es el atributo SmartPart sobre la definición de la clase. Este atributo es requerido para soportar la funcionalidad Inversión de Control (Inversion of Control – IoC) e Inyección de dependencias (Dependency Injection) provista por CAB.
Otro dato curioso es el operador partial en el encabezado de la clase. Ese operador indica que la clase está definida en más de un archivo.
También se observa el atributo CreateNew encima de la propiedad Presenter. Este atributo le indica al ObjectBuilder que cuando instancie la clase (mediante algún WorkItem) se encargue también de crear una nueva instancia de su presenter e inyectarla. Como se puede ver, la vista además vincula al presenter con ella misma mediante la propiedad View.
Por último se sobrescribe el método OnLoad() de la clase UserControl para poder avisarle al presenter cuando la vista se está cargando.
Presenter
CAB provee la clase Controller la cual puede ser usada como base para crear los presenters o controladores (si se usa el patrón MVC). Esta clase incluye una referencia al WorkItem padre y una propiedad para acceder a su estado (State). Sin embargo no es necesario usar la clase Controller para poder implementer a un presenter. A continuación se muestra una implementación de ejemplo de esta clase:
[C#]
public class Presenter
{
private IService _service;
private IView _view;
[ServiceDependency]
public IService Service
{
set { service = value; }
}
public IView View
{
get { return _view; }
set { _view = value; }
}
public override void OnViewReady()
{
// TODO: Agregar el codigo necesario para que la
// vista se cargue con datos validos.
}
// TODO: Agregar los metodos necesarios para manejar
// los eventos del usuario.
}
[Visual Basic]
Public Class Presenter
Private _service As IService
Private _view As IView
_
Public WriteOnly Property Service() As IService
Set(ByVal value As IService)
_service = value
End Set
End Property
Public Property View() As TView
Get
Return _view
End Get
Set(ByVal value As TView)
_view = value
End Set
End Property
Public Overrides Sub OnViewReady()
‘ TODO: Agregar el codigo necesario para que la
‘ vista se cargue con datos validos.
End Sub
‘ TODO: Agregar los metodos necesarios para manejar
‘ los eventos del usuario.
End Class
Como se vio anteriormente, el presenter posee una referencia a la vista mediante la interfaz IView. La encargada de cargar el valor en esta propiedad es la vista cuando ObjectBuilder le inyecta la nueva instancia de su presenter.
El presenter también es el responsable de acceder a los servicios necesarios y/o entidades del modelo de negocios (Business Entities). En el ejemplo anterior, cuando el ObjectBuilder cree una instancia de la clase Presenter, se encargará también de inyectar el servicio que implementa la interfaz IService (en los próximos post se profundizará más sobre este tema).
Dentro del método OnViewReady() se deberá colocar la lógica necesaria para cargar por primera vez los datos en la vista, en caso de que fueran necesarios, llamando a los métodos de la IView que se encargan de modificar cargar los datos que se muestran.
Además, es el presenter el que se encarga de publicar y/o suscribirse a todos los eventos necesarios. Para ello, CAB proporciona la herramienta EventBroker para facilitar esta tarea (este tema se explicará en más detalle en los próximos post).
Nota: Smar Client Software Factory trae una recipe llamada Add View (with presenter) la cual se encarga de crear las clases las clases con la estructura necesaria para implementar el patrón Model-View-Presenter. Además provee una clase abstracta y genérica llamada Presenter que usa como base para construir los presenter de cada vista.
TDD con MVP
Usando el patrón MVP se puede realizar fácilmente Test-Driven-Development (TDD). Por ejemplo se puede testear el presenter, independientemente de la implementación de la vista. Aislar al presenter en los tests es útil porque se puede enfocar toda la atención en probar sólo esta clase y no sus dependencias. Estas dependencias (como por ejemplo la vista y los servicios) son reemplazadas por implementaciones falsas (mock-implementations) lo cual es posible gracias a que el presenter tiene referencias sólo a interfaces y no a implementaciones concretas.
Para obtener una mejor visión de cómo aplicar TDD con el patrón MVP, recomiendo la lectura del post de Johnny Halife: How-To: Write MVP using TDD.
En el siguiente post se verá cómo utilizar UI Extension Sites y Commands.
-
2 Comments:
Leave a comment
Your email address will not be published.
lucasontivero said on September 19, 2007:
Excelente. Ahora, una sugerencia: para que el ejemplo de Vista-Presenter que diste se parezca aún más al diagrama con el que explicas el patrón le faltan dos cosas, una es una interface IView con algún método cualquiera a modo de ejemplo para que en el método OnViewReady() del Presenter puedas mostrar como este se comunica con la vista. El otro punto es que el Presenter necesita operar sobre el modelo (lo expresaste más que claramente) por lo que hubiese sido bueno que le inyectaras el WorkItem.
No lo tomes como cr’itica que si no estuviera tanta calidad no me gastar’ia ni en leerlo ni mucho menos hacer una sugerencia.
Saludos.
http:// said on September 19, 2007:
Muchas gracias Lucas por tus sugerencias. Siempre es bueno recibir feedback.
Tenés razón en que hubiese sido más claro agregar el código de la interfaz IView con algún método de ejemplo. Eso hubiese mostrado mejor la comunicación entre el presenter y la vista. Para mis próximos post lo voy a tener en cuenta.
En cuanto a lo de inyectar el WorkItem en el Presenter lo que suguerà es que si se necesita, se debe heredar de la clase Controller de CAB (la cual ya trae una propiedad para acceder al WorkItem padre y a su estado). Tal vez tendrÃa que haberlo agregado en el ejemplo para que quede más claro.
Saludos.