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.
Em uma descrição simples podemos definir como:
- View - em nosso caso é o formulário que exibirá os dados, não contém regra alguma do negócio a não ser disparar eventos que notificam mudança de estado dos dados que ele exibe e processamento próprio dele, como por exemplo código para fechar o formulário. Um objeto view implementa uma interface que expõe campos e eventos que o presenter necessita.
- Model - São os objetos que serão manipulados. Um objeto Model implementa uma interface que expõe os campos que o presenter irá atualizar quando sofrerem alteração na view.
- Presenter - É a ligação entre View e Model, possui papel de mediador entre eles. Ele é encarregado de atualizar o view quando o model é alterado e de sincronizar o model em relação ao view.
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;
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;
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;
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;
É 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;
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;
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.
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;
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;
Execute a aplicação e você que tudo funciona perfeitamente. Na figura 3 temos o diagrama de seqüência da aplicação.
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
-
Artigo
-
Artigo
-
Artigo
-
Artigo
-
Artigo