[C #] Implementando el patron MVP en .NET

El patrón Modelo Vista Presentador es una derivación del patrón MVC. Lo que se busca con este patrón es un desacople mayor entre la tecnología de interfaz d usuario y la lógica de la aplicación.

La implementación del patrón MVP presenta la siguiente estructura:

Entendemos entonces que el Modelo es la modelo de negocio como tal, la vista será nuestra tecnología de interfaz de usuario y el presentador será el encargado de desacoplar la comunicación entre el Modelo y la Vista. Un detalle importante con la implementación de este patrón es que será tal el desacople entre tecnología de interfaz de usuario y lógica, que se implementa una interface de por medio, es decir, la vista solo conocerá la definición del presentador para acudir y consumir sus métodos, pero nunca existirá un pasaje de información entre ellos debido al contrato que se declara entre la tecnología de interfaz de usuario y la interface (IVista).

Después de comprender las bases de lo que es el patrón MVP, veamos cómo integrar esto con nuestras aplicaciones. El ejemplo que haremos será muy sencillo, una aplicación que sume dos números, sé que es demasiado sencillo, pero en la complejidad del modelo no está el poder de MVP, está en la mantenibilidad del código y en el nivel de desacoplamiento que se puede llegar a lograr entre tecnología de interfaz de usuario y la lógica de la aplicación.

Para este ejemplo crearemos una solución en blanco de Visual Studio:

Sobre esta solución agregamos un nuevo proyecto del tipo librería de clases llamada Modelo y a este le agregaremos una clase llamada ModeloOperaciones, que expondrá un método público del tipo double llamado Sumar, así:

public class ModeloOperaciones
{
      public double Sumar(double num1, double num2)
      {
          return num1 + num2;
      }
}

Compilamos para que se genere el ensamblado de esta librería que hemos creado.

A la solución también le agregaremos un nuevo proyecto del tipo librería de clases llamado Presentador, sobre este agregamos una clase llamada PresentadorOperaciones y expondrá dos métodos del tipo void, uno llamado IniciarVista() y el otro llamado ActualizarVista(). Sobre este mismo proyecto agregaremos un elemento del tipo Interface y lo llamaremos IVistaOperaciones.

Sobre la interface IVista definiremos las propiedades que deberán ser implementadas por las tecnologías de interfaz de usuario, en nuestro caso expondremos las siguientes propiedades:

public interface IVistaOperaciones
{
        double Num1 { get; set; }
        double Num2 { get; set; }
        double Resultado { set; }
}

Como sabemos, el presentador será el único que conozca de la existencia de un modelo y una interfaz de usuario, aunque de hecho el presentador no sabe que tecnología es realmente, pues el trabajará con su contrato el IVista, por eso necesitamos ahora agregar una referencia a nuestro proyecto de Modelo y escribir el método constructor de esta clase para inicializar el objeto de la interfaz (recuerden que no se pueden crear instancias de una interfaz). El código de nuestra clase nos queda así:

public class PresentadorOperaciones
{
        private readonly IVistaOperaciones _vista;
        private ModeloOperaciones _modelo;
        public PresentadorOperaciones(IVistaOperaciones vista)
        {
            _vista = vista;
            _modelo = new ModeloOperaciones();
        }
        public void IniciarVista()
        {
            _vista.Num1 = 0;
            _vista.Num2 = 0;
            _vista.Resultado = 0;
        }
        public void ActualizarVista()
        {
            _vista.Resultado = _modelo.Sumar(_vista.Num1, _vista.Num2);
        }
}

Con esto ya tenemos todo el «cascaron» de lo que debemos consumir, ahora construyamos un «consumidor». Sobre esta solución agreguemos ahora un proyecto del tipo Windows Forms y sobre este agreguemos dos TextBox, un botón y un Label, así:

A este proyecto le agregaremos una referencia de el ensamblado de nuestro proyecto Presentador. Del lado del código de nuestro formulario, lo primero que debemos hacer es implementar de la interface IVista, así:

public partial class Form1 : Form, IVistaOperaciones
{
        public Form1()
        {
            InitializeComponent();
        }
        public double Num1
 	{
		 get { return !string.IsNullOrEmpty(textBox1.Text) ? Convert.ToDouble(textBox1.Text) : 0; }
		 set { textBox1.Text = value.ToString(); }
	}
	public double Num2
	{
		get { return !string.IsNullOrEmpty(textBox2.Text) ? Convert.ToDouble(textBox2.Text) : 0; }
		set { textBox2.Text = value.ToString(); }
	}
	public double Resultado
	{
		set { label1.Text = value.ToString(); }
	}
}

Como se pueden dar cuenta, al implementar de la interface obligamos  a esta clase (Form1.cs) a implementar todos los miembros definidos en esta. La única lógica que debiera existir en esta clase es la de validaciones, como se ve en esta.

Ahora debemos crear un objeto de nuestro presentador, que será el único que conozca de la existencia del modelo y por tanto el único capaz de consumir sus métodos. Nuestra clase Form1.cs nos quedará así:

public partial class Form1 : Form, IVistaOperaciones
{
        private readonly PresentadorOperaciones _operaciones;
        public Form1()
        {
            InitializeComponent();
            _operaciones = new PresentadorOperaciones(this);
        }
        #region Implementation of IVistaOperaciones
        public double Num1
        {
            get { return !string.IsNullOrEmpty(textBox1.Text) ? Convert.ToDouble(textBox1.Text) : 0; }
            set { textBox1.Text = value.ToString(); }
        }
        public double Num2
        {
            get { return !string.IsNullOrEmpty(textBox2.Text) ? Convert.ToDouble(textBox2.Text) : 0; }
            set { textBox2.Text = value.ToString(); }
        }
        public double Resultado
        {
            set { label1.Text = value.ToString(); }
        }
        #endregion

        private void Form1_Load(object sender, EventArgs e)
        {
            _operaciones.IniciarVista();
        }
        private void button1_Click(object sender, EventArgs e)
        {
            _operaciones.ActualizarVista();
        }
}

Como podemos ver, la instancia del presentador tiene una relación directa con la tecnología de interfaz de usuario, porque realmente esta se entenderá con un contrato que no define más que el miembro a implementar.

Si ejecutamos vemos que después de todo esto, nuestra aplicación funciona, lo hemos logrado, pero dejar este post hasta aquí seria muy corto y no veríamos nada el potencial de MVP, que pasaría si tu cliente te dice «¡Ahora quiero sumar en internet! necesito ese proyecto en cinco minutos!!» pues bien, no es problema, simplemente agregamos un proyecto del tipo ASP.NET implementamos de nuestra Interface y listo!

En nuestro proyecto de asp.net en una pagina .aspx creamos el mismo diseño que en el de windows forms, y del lado del código implementamos los miembros de la interface y creamos la instancia del presentador así:

public partial class _Default : System.Web.UI.Page, IVistaOperaciones
    {
        private PresentadorOperaciones _presentador;
        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);
            _presentador = new PresentadorOperaciones(this);
            Load += Page_Load;
        }
        protected void Page_Load(object sender, EventArgs e)
        {
            if(!IsPostBack)
            {
                _presentador.IniciarVista();
            }
        }
        protected void btSumClick(object sender, EventArgs e)
        {
            _presentador.ActualizarVista();
        }
        #region Implementation of IVistaOperaciones

        public double Num1
        {
            get { return !string.IsNullOrEmpty(txtNum1.Text) ? Convert.ToDouble(txtNum1.Text) : 0; }
            set { txtNum1.Text = value.ToString(); }
        }
        public double Num2
        {
            get { return !string.IsNullOrEmpty(txtNum2.Text) ? Convert.ToDouble(txtNum2.Text) : 0; }
            set { txtNum2.Text = value.ToString(); }
        }
        public double Resultado
        {
            set { lbResultado.Text = value.ToString(); }
        }
        #endregion
}

Ese es realmente el potencial de MVP, la lógica de la aplicación le debe importar un pepino la tecnología de interfaz de usuario y la interfaz de usuario deberá llevar la menor lógica posible, así el mantenimiento de estas es más sencillo y el cambio de tecnología de interfaz de usuario es pan comido.

Espero les sea de utilidad.

Descarga el ejemplo (MVPApp)

Hasta el próximo post.

25 comentarios sobre “[C #] Implementando el patron MVP en .NET

  1. Quiser hacer una preguntaa mi dudad es esta:
    Tengo un boton Ver Excel pero quiero que este boton me abra un archivo en excel 2007 que eh echo … Lo que he estado haciendo es este codigo:
    // System.Diagnostics.Process.Start(Application.StartupPath + «/Excel»);
    Donde Excel es mi carpeta donde recien se encuentra mi achivo excel.xlsx
    Pero abre la carpeta donde esta el excel que eh creado mas no me abre el archivo directamente como podria solucionarlo Gracias…

    1. Hola, antes que nada que pena por la tardanza al responder, he tenido mucho trabajo en estos dias.

      Ahora si veamos tu problema. Estas bien, al hacer Process.Start(), lo que haces es iniciar un recurso de proceso y lo asocia a un componente Process (mas info aqui: http://msdn.microsoft.com/es-es/library/system.diagnostics.process.start.aspx). Como parametro pasas Application.StartupPath + «/Excel» siendo «Excel» una carpeta. Ahi esta tu error. Para asociar un recurso a un proceso (en este caso el Excel) no puedes pasar el nombre de un folder, debes pasar el nombre del archivo con SU EXTENCION. Es decir:
      Application.StartupPath + «\Excel\file.xlsx» FIJATE EN LA BARRA INVERTIDA. De esta forma ya abres sin problema el archivo de Excel 😀
      NOTA: Application.StartupPath Obtiene la ruta de acceso del archivo ejecutable que inició la aplicación, sin incluir el nombre del archivo ejecutable en este caso «\bin\Debug» no se si realmente quieras ubicar alli tus archivos de Excel.

      Gracias por la pregunta, espero te sea de ayuda.

  2. Tengo una preguntaa.. lo explicare lo mas sencillo posible:
    Quiero imprimir defrente de un datatable ( en este datatable pondre mi store procedure ) y luego que cuando le de al boton imprimir me imprima desde la impresora prderteminada como podria serlo ,.. he visto por ahi printDocument.

  3. Gracias por la explicación, ha facilitado al menos la compresión teórica del modelo.
    Quisiera preguntarle de todas formas cierta duda:
    – ¿En este modelo, es posible la utilización de AJAX y updatePanels?
    Gracias

  4. Una pregunta, como regla de MVP debe existir un presentador por cada formulario ? o se puede usar un presentador para mas de un formulario?
    Es aconsejable tener mas de un presentador por formulario?

    Muchas Gracias.

    1. Hola.
      Q: «como regla de MVP debe existir un presentador por cada formulario o se puede usar un presentador para mas de un formulario?»
      A: Como todo… depende, si se ajusta o no a tus necesidades y diseño. No hay reglas de oro que se deban aplicar si o si. Lo que pasaría es que se violaría el principio de unica responsabilidad si se implementa un diseño como ese.

      Q: Es aconsejable tener mas de un presentador por formulario?
      A: Va ligado a la primera pregunta. Habrían dos presentadores soportando el mismo IView y por qué? La vista estará soportando más de un View y por qué? Son cuestiones de diseño, que en principio pueden verse raras o erradas, pero que en definitiva dependerán de las necesidades a las que se deba dar solución.

      Plantea bien como deberían ser representadas estas necesidades en la interfaz de usuario y construye de ahí hacía abajo… a ver si consigues un diseño con menos smells.

  5. Hola pero el patrón MVP es un patrón de diseño que va implementado en la capa de presentacion por que creas varios proyectos?

    1. Hola Pedro, gracias por tu comentario. Te ha pasado que al abrir un proyecto antiguo piensas: iughh, ¿yo programaba así? 😛 a veces pasa también con el código que dejas en el blog… 😛 Saludos

Replica a Pedro Ávila Núñez Cancelar la respuesta