NMvp, demonstração

Hoje vamos ver como aplicar o padrão MVP mencionado no post anterior. Para tal, vamos utilizar o framework NMVP. O NMVP encapsula e implementa para nós os requisitos mínimos para utilização do padrão. Mãos a obra. O projeto é simples para demonstrar como utilizar o NMVP. Consiste apenas de uma tela de cadastro.

A primeira coisa a fazer é baixar o framework acessando sua página no site codeplex. Inicie uma nova solução no VS2005 e a nomeie como desejar. Adicione na solução um novo projeto em c#, do tipo class library com o nome de domain. Nele, adicione a classe da listagem 1.

Listagem 1 – Classe Pessoa

   1:  namespace nmvpDemo.domain
   2:  {
   3:      public class Pessoa
   4:      {
   5:          private string _nome;
   6:   
   7:          public string Nome
   8:          {
   9:              get { return _nome; }
  10:              set { _nome = value; }
  11:          }
  12:   
  13:          private string _endereco;
  14:   
  15:          public string Endereco
  16:          {
  17:              get { return _endereco; }
  18:              set { _endereco = value; }
  19:          }
  20:   
  21:          private string _telefone;
  22:   
  23:          public string Telefone
  24:          {
  25:              get { return _telefone; }
  26:              set { _telefone = value; }
  27:          }
  28:   
  29:          public Pessoa()
  30:          {
  31:              Nome = string.Empty;
  32:              Endereco = string.Empty;
  33:              Telefone = string.Empty;
  34:          }
  35:      }
  36:  }
 

Como disse anteriormente o padrão é composto por Model, View e Presenter. Neste momento vamos adicionar um novo projeto, também do tipo class library, como nome de mvp. Vamos criar o Model.

 

Criando o Model

Adicione uma referência ao assembly domain.dll, do projeto domain e ao NMvp.dll, contido na pasta Binaries.

Este model define o que a view concreta deverá conter e será o meio de acesso do presenter. Nosso model deve implementar IContract. Como mencionei anteriormente o framework encapsulou o “trabalho chato” que seria a implementação básica do relacionamento da tríade Model-View-Presenter portanto este é o porque de estarmos herdando de tipos pré-definidos.

Adicione uma nova interface ao projeto, mudando seu nome para ICadastroPessoa e construa a interface como na listagem 2

Listagem 2 – Contrato que View e Presenter devem obedecer

   1:  using NMvp;
   2:   
   3:  namespace nmvpDemo.mvp
   4:  {
   5:      public interface ICadastroPessoa: IContract
   6:      {
   7:          /// <summary>
   8:          /// Evento que é disparado quando a inclusão deve ser salva
   9:          /// </summary>
  10:          event EventHandler Salvar;
  11:   
  12:          /// <summary>
  13:          /// Evento que é disparado quando a inclusão é cancelada
  14:          /// </summary>
  15:          event EventHandler Cancelar;
  16:   
  17:   
  18:          string Nome
  19:          {
  20:              get;
  21:              set;
  22:          }
  23:   
  24:          string Endereco
  25:          {
  26:              get;
  27:              set;
  28:          }
  29:   
  30:          string Telefone
  31:          {
  32:              get;
  33:              set;
  34:          }
  35:   
  36:          /// <summary>
  37:          /// Indica se o objeto foi salvo ou não
  38:          /// </summary>
  39:          bool EstaSalvo
  40:          {
  41:              get;
  42:              set;
  43:          }
  44:      }
  45:  }

Criando o Presenter

Vamos criar o presenter agora. Nele está podemos dizer que contém a lógica da View concreta. Adicione uma nova classe, CadastroPessoaPresenter, que descenderá de Presenter e nele vamos implementar conforme listagem 3

Listagem 3 – Presenter

   1:  using NMvp;
   2:  using nmvpDemo.domain;
   3:   
   4:  namespace nmvpDemo.mvp
   5:  {
   6:      public class CadastroPessoaPresenter: Presenter<ICadastroPessoa>
   7:      {
   8:          
   9:          public CadastroPessoaPresenter(ICadastroPessoa view):base(view){}
  10:          
  11:          /// <param name="isFirstCall">
  12:          /// Indicates whether this is the first call
  13:          ///             to this method.
  14:          /// </param>
  15:          protected override void InitCore(bool isFirstCall)
  16:          {
  17:              this.View.Salvar += delegate(object sender, EventArgs e) { this.SaveCore(); };
  18:              this.View.Cancelar += delegate(object sender, EventArgs e) { this.View.EstaSalvo = false; };
  19:          }
  20:   
  21:          /// <param name="isFirstCall">
  22:          /// Indicates whether this is the first call
  23:          ///             to this method.
  24:          /// </param>
  25:          protected override void LoadCore(bool isFirstCall)
  26:          {
  27:              if (isFirstCall)
  28:              {
  29:                  this.View.EstaSalvo = false;
  30:              }
  31:          }
  32:   
  33:          protected override void SaveCore()
  34:          {
  35:              Pessoa pessoa = new Pessoa();
  36:              pessoa.Nome = this.View.Nome;
  37:              pessoa.Endereco = this.View.Endereco;
  38:              pessoa.Telefone = this.View.Telefone;
  39:              this.View.EstaSalvo = true;
  40:          }
  41:      }
  42:  }

Observe que o presenter não está ligado diretamente a uma view implementada e sim acessa a view através de um contrato, que no nosso exemplo é o ICadastroPessoa. Toda view real vai implementar esse contrato, dessa maneira podemos ter várias views para um mesmo presenter. Isso é ótimo !

Imagine que você pode criar um interface utilizando Windows forms, e depois que passar essa interface para WPF ou ASP.NET. Basta fazer com que a sua view de cada tecnologia implemente o IContract definido, inicialmente, nem no presenter será necessária alguma alteração.

Criando a View

Adicione um novo projeto do tipo Windows forms, dê o nome de UI.Windows. Já adicione uma referência ao assembly mvp.dll. Veja que não referenciamos nosso domai.dll, isso significa que a interface de usuário está desacoplada do domínio e vice-versa.

Construa o formulário de acordo com a figura 1.

 

Cadastro

Figura 1 – Cadastro de pessoal

Esse formulário é view concreta e deve implementar nosso contrato ICadastroPessoa. Vamos alterar isso e implementar o contrato. Na listagem 4 temos o código.

Listagem 4 – Formulário View

   1:  using nmvpDemo.mvp;
   2:   
   3:  namespace UI.Windows
   4:  {
   5:      public partial class CadastroPessoa : Form, ICadastroPessoa
   6:      {
   7:          public CadastroPessoa()
   8:          {
   9:              InitializeComponent();
  10:          }
  11:   
  12:          #region Implementação do Model ICadastroPessoa
  13:          
  14:          private bool _estaSalvo;
  15:   
  16:          public string Nome
  17:          {
  18:              get{ return this.txtNome.Text;}
  19:              set{ this.txtNome.Text = value;}
  20:          }
  21:   
  22:          public string Endereco
  23:          {
  24:              get { return this.txtEndereco.Text; }
  25:              set { this.txtEndereco.Text = value; }
  26:          }
  27:   
  28:          public string Telefone
  29:          {
  30:              get { return this.txtTelefone.Text; }
  31:              set { this.txtTelefone.Text = value; }
  32:          }
  33:   
  34:          public bool EstaSalvo
  35:          {
  36:              get { return this._estaSalvo;}
  37:              set
  38:              {
  39:                  _estaSalvo = value;
  40:                  btnSalvar.Enabled = !this._estaSalvo;
  41:              }
  42:          }
  43:   
  44:          public bool IsValid
  45:          {
  46:              get
  47:              {
  48:                  return (txtNome.Text.Length > 0);
  49:              }
  50:          }
  51:   
  52:   
  53:          private bool isReadOnly = false;
  54:          /// <summary>
  55:          /// Indicates if the form is in read only mode.
  56:          /// </summary>
  57:          public bool IsReadOnly
  58:          {
  59:              get { return isReadOnly; }
  60:              set { isReadOnly = value;}
  61:          }
  62:   
  63:          public event EventHandler Salvar;
  64:          public event EventHandler Cancelar;
  65:          
  66:          #endregion
  67:   
  68:          private void btnSalvar_Click(object sender, EventArgs e)
  69:          {
  70:              if (this.Salvar != null)
  71:              {
  72:                  this.Salvar(this, EventArgs.Empty);
  73:              }
  74:   
  75:          }
  76:   
  77:          private void btnCancelar_Click(object sender, EventArgs e)
  78:          {
  79:              if (this.Cancelar != null)
  80:              {
  81:                  this.Cancelar(this, EventArgs.Empty);
  82:              }
  83:          }
  84:      }
  85:  }

Veja que implementamos o contrato e inclusive nos cliques dos botões o código a ser executado é o que o presenter está implementado. A lógica está fora da interface de usuário.

Com o MVP pronto vamos alterar o Main para carregar nossa aplicação. Altere o Main conforme listagem 5.

Listagem 5 – Executando

   1:  using nmvpDemo.mvp;
   2:   
   3:  namespace UI.Windows
   4:  {
   5:      static class Program
   6:      {
   7:          /// <summary>
   8:          /// The main entry point for the application.
   9:          /// </summary>
  10:          [STAThread]
  11:          static void Main()
  12:          {
  13:              Application.EnableVisualStyles();
  14:              Application.SetCompatibleTextRenderingDefault(false);
  15:   
  16:              CadastroPessoa view = new CadastroPessoa();
  17:              CadastroPessoaPresenter presenter = new CadastroPessoaPresenter(view);
  18:              presenter.Init(true);
  19:              presenter.Load(true);
  20:   
  21:              Application.Run(view);
  22:          }
  23:      }
  24:  }

Ao executar você verá que o botão salvar após ser clicado mudará seu estado. Na view, isto está implementado EstaSalvo, porém em nenhum momento na view nós alteramos seu valor. Talvez se você ainda estiver um pouco perdido pode se perguntar como que o estado do botão está mudando… eu lhe digo.. é o presenter que está setando o valor da propriedade EstaSalvo definida pelo contrato. Veja isso no InitCore do presenter. Nele os eventos Cancelar eSalvar são implementados.

Conclusão

A partir daqui você já pode utiliza o NMvp para separar a interface do usuário do resto da sua aplicação, como desafio, tente implementar um outro formulário diferente e no Main chame esse seu novo formulário, você verá que não precisará alterar o presenter. Além de conseguirmos essa flexibilidade, podemos com seu uso, tornar nossas Views testáveis.

Até mais.

 

Esse post foi publicado em Não categorizado. Bookmark o link permanente.

2 respostas para NMvp, demonstração

  1. Eduardo disse:

    Paulo,
     
    Você podia falar um pouco da vantagem de usar o NMvp, o que ele está fazendo por debaixo do pano que traz benefício para a arquitetura da solução.
     
    Eu achei a solução muito parecida com uma implementação de MVP sem framework. Qual seria então a vantagem de colocar o NMvp na minha arquitetura? Na verdade estou com preguiça de estudar o NMvp e já que vc botou a mão na massa… 😉
     

  2. Bruno disse:

    Acho que o MVP puro não teria essa delegação do evento para o Presenter. Esse modelo estaria mais para o Supervising Controller, que é um refinamento do MVP pelo Fowler.
     
    Mas ainda teriamos algum código no nosso code-behind (imaginando que não estamos usando um web user control como View). Ao que parece, o outro modelo proposto Passive View, seria para casos mais complexos e não teriam nenhum código na UI.
     
    Preciso estudar mais o modelo do NMVP. Se alguém puder contribuir. Vou fazer alguns posts no blog (http://brunokenj.ms) se quiserem acompanhar também.
     
    abraços,

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s