O Padrão MVP (Model-View-Presenter)

Conheça nesse artigo de Paulo Quicoli, o padrão MVP (Model-View-Presenter).

O padrão MVP tem a finalidade de separar a camada de apresentação das camadas de dados e regras de negócio. Neste artigo vamos apresentar como usar o MVP em uma aplicação Delphi.

O MPV é divido em três partes bem distintas e com responsabilidades específicas, são elas o Model, View e Presenter, conforme figura 1.

Figura 1. MVP

Em uma descrição simples podemos definir como:

Demonstração

Nosso aplicativo de exemplo será simples, porém irá mostrar como iniciar o desenvolvimento de um aplicativo utilizando o padrão MVPe fazendo uso de interfaces.

Constará apenas de uma tela de Login que será exibida antes do formulário principal da aplicação, validando o usuário. Inicie uma nova aplicação Win32 no Delphi 2006 ou Delphi 7.

O Model

Crie uma nova unit e a adicione ao projeto, salvando-a como UsuarioIntf.pas e declare a seguinte interface da listagem 1.

IUsuario = interface ['{EC207CEB-2C68-4EC3-92D6-0748E8B64882}'] procedure SetSenha(val : string); function GetSenha : string; procedure SetNome(val : string); function GetNome : string; function Login: boolean; property Nome : string read GetNome write SetNome; property Senha : string read GetSenha write SetSenha; end;
Listagem 1. Interface para usuário

Vamos agora criar a classe que implementa nossa interface. Adicione outra unit ao projeto salvando-a como Usuario.pas e definindo a classe como listagem 2.

uses UsuarioIntf; type TUsuario = class(TInterfacedObject, IUsuario) private FNome:String; FSenha:String; protected procedure SetNome(val:string); function GetSenha: String; function GetNome: String; procedure SetSenha(val:string); public property Senha : String read GetSenha write SetSenha; property Nome : String read GetNome write SetNome; function Login:boolean; end;
Listagem 2. Classe TUsuario

Você pode implementar os Gets e Sets das propriedades, neles não tem segredo algum, na listagem 3 temos o método Login, que aqui contém uma senha fixa apenas para simplificar o exemplo, mas o correto seria buscar essa senha em algum banco de dados.

function TUsuario.Login: boolean; begin result := (self.FNome = 'ClubeDelphi') and (self.FSenha = 'MVP'); end;
Listagem 3. Método Login

Interfaces da View e Presenter

Como já mencionado, a View é a representação visual de nossos objetos, é nela que o usuário vai interagir. Observe agora a figura 1, veja que uma view possui referência para um presenter e nunca diretamente para um model.

Em uma nova unit adicione crie a interface IUsuarioView, conforme listagem 4 e salve-a como UsuarioMVPIntf.

uses UsuarioIntf, Controls; type IUsuarioPresenter = interface; IUsuarioView = interface ['{10EC2701-D328-45E9-AC26-9889C7F3ECA3}'] procedure SetSenha(val : string); function GetSenha : string; procedure SetNome(val : string); function GetNome : string; procedure SetPresenter(const val : IUsuarioPresenter); function GetPresenter: IUsuarioPresenter; property Nome: string read GetNome write SetNome; property Senha: string read GetSenha write SetSenha; property Presenter: IUsuarioPresenter read GetPresenter write SetPresenter; function ShowView: TModalResult; end;
Listagem 4. Interface da View

É preciso também a interface do presenter, que também contém uma referência à View. Veja novamente a figura 1. Nela vemos que é o presenter que atutaliza o Model, dessa forma temos aqui uma referência ao Model. Veja como ficou a interface de nosso presenter na listagem 5.

IUsuarioPresenter = interface ['{56BBCC2F-E0AA-40CA-B0C6-95A499E80410}'] procedure SetModel(const Value: IUsuario); procedure SetView(const Value: IUsuarioView); function GetModel: IUsuario; function GetView: IUsuarioView; function Login: boolean; procedure InitiView; property Model: IUsuario read GetModel write SetModel; property View: IUsuarioView read GetView write SetView; end;
Listagem 5. Interface do Presenter

Veja a referência que o presenter faz ao model. Ele aponta para a interface e não para a classe concreta. Em ponto algum do MVP lidamos com o model concreto, as interfaces aqui são utilizados como uma espécie de contrato que todas as partes envolvidas devem utilizar, dessa forma conseguimos separar, encapsular, a classe TUsuario de tal forma que tanto a View como o Presenter não sabem quem a está implementando, porque enxergam apenas o que o “contrato” permite. Esse conceito de ter referências para interfaces é uma das bases de inversão de controle, que fica para um artigo futuro.

Implementando o Presenter

O presenter, como já dito, é o mediador entre a view e o model. Na listagem 6 vemos sua implementação, atente para o método Login.

uses UsuarioIntf, UsuarioMVPIntF; type TLoginPresenter = class(TInterfacedObject,IUsuarioPresenter) private FModel: IUsuario; FView: IUsuarioView; protected procedure SetModel(const Value: IUsuario); procedure SetView(const Value: IUsuarioView); function GetModel: IUsuario; function GetView: IUsuarioView; public property Model: IUsuario read FModel write SetModel; property View: IUsuarioView read FView write SetView; function Login: boolean; procedure InitiView; end; (...) function TLoginPresenter.Login: boolean; begin model.Nome := View.Nome; model.Senha := View.Senha; result := Model.Login; end;
Listagem 6. classe TLoginPresenter

O presenter, no método Login, obtém da view os valores que precisam ser atualizados no model. O presenter não sabe detalhes de como são gerados os valores, ele apenas os obtém através das propriedades e passa para o model, conforme figura 1.

Implementando a View

Adicione um novo Formulário e o salve sua unit como LoginU.pas. Ajuste sua aparência como como na figura 2.

Figura 2. Tela de Login

Esse formulário irá implementar nossa interface IUsuarioView. Na listagem 7 temos o código.

type TLoginF = class(TForm, IUsuarioView) Label1: TLabel; EditUsuario: TEdit; Label2: TLabel; EditSenha: TEdit; btLogin: TButton; btCancelar: TButton; procedure btCancelarClick(Sender: TObject); procedure btLoginClick(Sender: TObject); private Fpresenter: Pointer; FNome: string; FSenha: string; procedure Setpresenter(const Val: IUsuarioPresenter); procedure SetSenha(val : string); function GetSenha : string; procedure SetNome(val : string); function GetNome : string; function Getpresenter: IUsuarioPresenter; public { Public declarations } property presenter: IUsuarioPresenter read Getpresenter write Setpresenter; property Nome : string read GetNome write SetNome; property Senha : string read GetSenha write SetSenha; function ShowView: TModalResult; end; implementation {$R *.dfm} procedure TLoginF.btCancelarClick(Sender: TObject); begin ModalResult := mrCancel; end; procedure TLoginF.btLoginClick(Sender: TObject); begin if Presenter.Login then ModalResult := mrOk else ShowMessage('Usuário inválido'); end; function TLoginF.GetNome: string; begin result := EditUsuario.Text; end; function TLoginF.Getpresenter: IUsuarioPresenter; begin result := IUsuarioPresenter(FPresenter); end; function TLoginF.GetSenha: string; begin result := EditSenha.Text; end; procedure TLoginF.SetNome(val: string); begin Self.FNome := val; EditUsuario.Text := val; end; procedure TLoginF.Setpresenter(const Val: IUsuarioPresenter); begin FPresenter := Pointer(Val); end; procedure TLoginF.SetSenha(val: string); begin Self.Senha := val; EditSenha.Text := val; end; function TLoginF.ShowView: TModalResult; begin result:= self.ShowModal; end;
Listagem 7. View implementada por TLoginF

Na seção public temos a declaração das propriedades e métodos que a interface IUsuarioView nos obriga a realizar. Observe também que nos Gets e Sets das propriedades Nome e Senha não temos referencia ao model nem ao presenter, apenas passamos os valores paras propriedades.

Agora você pode se perguntar, e como faço pra validar o usuário? Veja o método btLoginClick. Nele chamamos o método Login do presenter e não do model, ou seja, nosso formulário de login não sabe como funciona o Login, sua implementação está encapsulada.

Executando

No evento onShow do formulário principal vamos configurar o ambiente do MVP, ou seja, definir o presenter, o model e a view e executá-la., confirma a listagem 8.

procedure TPrincF.FormShow(Sender: TObject); var lview : IUsuarioView; lusuario: IUsuario; lpresenter: IUsuarioPresenter; begin lview := TLoginF.Create(nil); lpresenter := TLoginPresenter.Create; lusuario := TUsuario.create; lpresenter.Model := lusuario; lpresenter.View := lview; if lview.ShowView = mrCancel then begin Close; end; end;
Listagem 8. OnShow

Execute a aplicação e você que tudo funciona perfeitamente. Na figura 3 temos o diagrama de seqüência da aplicação.

Figura 3. Diagrama de seqüência

Conclusão

Agora você deve estar pensando... “é muito código para um login!”. Sim, concordo com você se estiver vendo apenas pelo login. Digo que não dá para demonstrar todas as vantagens de se separar a camada de apresentação das regras de negócio de forma eficiente apenas com uma tela de login. Porém posso apontar aqui um fato muito importante, na view não temos referência ao model, a regra de negócio não está na view, assim se quisermos mudar a tela de login, por exemplo substituindo os botões comuns por ou outro tipo de botão ou qualquer coisa diferente, não alteraria a funcionalidade da janela. Ao programar no modelo “convencional”, acabamos quase sempre por colocar as regras dentro das janelas, e se precisarmos mudar a janela, podemos quebrar a regra, gerando um erro no programa, ou então fazemos nossas regras depender muito das visualizações (formulários e eventos de formulários) que dificultam qualquer alteração. A boa prática que mostrar é que precisar separar as regras das interfaces de usuário, e colocar menos código possível nessas interfaces, para torná-las independentes.

Talvez mesmo assim, você desanime da quantidade de código que foi preciso, mas não se preocupe, existe hoje o framework Infra que possui a implementação de um MVP completo para os componentes VCL do Delphi, reduzindo muito o código necessário.

Abraços e até a próxima.

Artigos relacionados