A primeira dificuldade que um desenvolvedor RAD que está começando a mudar seu pensamento para o OO enfrenta é sobre a persistência dos objetos, a segunda é como exibir os objetos nos formulários, Figura 1.
A facilidade com que podemos ligar componentes data-aware à tabelas de um SGBD e sua sincronização automática leva os desenvolvedores a permitir, sem questionamento algum, que tecnologias relacionais modelem a estrutura de seus sistemas. Isso conduz a sistemas dependentes de certos bancos de dados, difíceis de manter e que não conseguem representar toda a complexidade de uma regra de negócios.
O padrão Model-Gui-Mediator, ou simplesmente MGM, oferece uma solução bastante simples para essa dificuldade permitindo a criação de componentes “object-aware”, fazendo com o que o desenvolvedor possa criar um modelo de negócios e apresentá-lo em componentes GUI de forma eficiente.
Apresentação
O padrão MGM é composto por dois padrões de projeto, o Observer e o Mediator. Ele se utiliza desses dois padrões para criar “mediating views”, que nada mais são do que classes que vão intermediar a comunicação entre os objetos que compõe seu modelo de negócios e os componentes visuais que os representarão ao usuário de maneira que estarão desacoplados ao ponto de podermos ter uma mesma propriedade de um objeto sendo vista em um TEdit e em um TLabel por exemplo.
Terminologia
O MGM apresenta os seguintes termos:
- Model – São os objetos de negócio que se deseja visualizar
- Mediating Views – Este é o coração do padrão. Saõ classes que implementam o padrão observer para esperarem por mudanças nos objetos de negócio, intermediam a relação entre objetos e a GUI e entre GUI e objetos.
- GUI – São os controles visuais, componentes como TEdit, TLabel, TListView que serão utilizados para manipular os valores dos objetos e vice-versa.
Na Figura 2 vemos como que se dá o funcionamento do padrão MGM.
Implementação
Todo padrão possui seu diagrama de classes, na Figura 3 temos uma visão conceitual do MGM.
Inicialmente os objetos de negócio devem implementar o Subject do padrão Observer. Posteriormente nossas classes que atuarão como “Medianting Views” deverão implementar o Observer e terão o código responsável para exibir os atributos dos objetos em componentes GUI.
Tendo construído um Mediating View para cada propriedade de um objeto que se deseja exibir, os adicionamos à lista de observers do objeto, lembrando que um medianting view possui uma referência ao controle GUI que será utilizado.
Um exemplo prático
Temos a classe TContato que já implementa o Subject do padrão Observer, conforme visto na listagem 1, e queremos visualizar seus dados em um formulário como na figura 4.
TContato = class(TSubject)
private
FNome: string;
FEndereco: string;
FTelefone: string;
procedure SetNome(const Value: string);
procedure SetEndereco(const Value: string);
procedure SetTelefone(const Value: string);
protected
public
property Nome: string read FNome write SetNome;
property Endereco: string read FEndereco write SetEndereco;
property Telefone: string read FTelefone write SetTelefone;
end;
implementation
{ TContato }
procedure TContato.SetEndereco(const Value: string);
begin
FEndereco := Value;
Notify;
end;
procedure TContato.SetNome(const Value: string);
begin
FNome := Value;
Notify;
end;
procedure TContato.SetTelefone(const Value: string);
begin
FTelefone := Value;
Notify;
end;
Como o padrão sugere temos que criar para cada propriedade que se deseja exibir uma classe mediadora. Essa classe mediadora deve implementar o Observer do padrão observer e é a responsável em saber como atualizar o controle GUI.
Sendo assim, acredito que seja melhor criar uma unit que conterá todas as implementações básicas para cada controle GUI que vamos utilizar. Na Listagem 2 temos a implementação para três controles básicos, um TLabel, um TEdit e um TMakEdit.
TEditMediatorView = class(TObserver)
private
FEdit: TEdit;
procedure SetEdit(const Value: TEdit);
public
constructor Create(const Edit: TEdit); virtual;
destructor Destroy; override;
property Edit: TEdit read FEdit write SetEdit;
procedure OnTextChanged(sender: TObject); virtual; abstract;
end;
TMaskEditMediatorView = class(TObserver)
private
FMaskEdit: TMaskEdit;
procedure SetMaskEdit(const Value: TMaskEdit);
public
constructor Create(const MaskEdit: TMaskEdit); virtual;
destructor Destroy; override;
property MaskEdit: TMaskEdit read FMaskEdit write SetMaskEdit;
procedure OnTextChanged(sender: TObject); virtual; abstract;
end;
TLabelStringMediatorView = class(TObserver)
private
FaLabel: TLabel;
procedure SetaLabel(const Value: TLabel);
public
constructor Create(const aLabel: TLabel); virtual;
destructor Destroy; override;
property aLabel: TLabel read FaLabel write SetaLabel;
end;
implementation
{ TEditMediatorView }
constructor TEditMediatorView.Create(const Edit: TEdit);
begin
inherited Create;
FEdit := Edit;
FEdit.OnChange := OnTextChanged;
end;
destructor TEditMediatorView.Destroy;
begin
Edit.OnChange := nil;
Edit := nil;
inherited;
end;
procedure TEditMediatorView.SetEdit(const Value: TEdit);
begin
FEdit := Value;
end;
{ TMaskEditMediatorView }
constructor TMaskEditMediatorView.Create(const MaskEdit: TMaskEdit);
begin
FMaskEdit := MaskEdit;
FMaskEdit.OnChange := OnTextChanged;
end;
destructor TMaskEditMediatorView.Destroy;
begin
MaskEdit.OnChange := nil;
MaskEdit := nil;
inherited;
end;
procedure TMaskEditMediatorView.SetMaskEdit(const Value: TMaskEdit);
begin
FMaskEdit := Value;
end;
{ TLabelStringMediatorView }
constructor TLabelStringMediatorView.Create(const aLabel: TLabel);
begin
FaLabel := aLabel;
end;
destructor TLabelStringMediatorView.Destroy;
begin
FaLabel := nil;
inherited;
end;
procedure TLabelStringMediatorView.SetaLabel(const Value: TLabel);
begin
FaLabel := Value;
end;
Essas classes serão utilizadas como base para as classes que cuidarão de cada propriedade dos objetos TContato. Veja a Listagem 3.
TContatoNomeView = class(TEditMediatorView)
public
procedure Update; override;
procedure OnTextChanged(Sender: TObject); override;
end;
TContatoNomeLabelView = class(TLabelStringMediatorView)
public
procedure Update; override;
end;
TContatoEnderecoView = class(TEditMediatorView)
public
procedure Update; override;
procedure OnTextChanged(Sender: TObject); override;
end;
TContatoTelefoneView = class(TMaskEditMediatorView)
public
procedure Update; override;
procedure OnTextChanged(Sender: TObject); override;
end;
implementation
{ TContatoNomeView }
procedure TContatoNomeView.OnTextChanged(Sender: TObject);
begin
TContato(FSubject).Nome := Edit.Text;
end;
procedure TContatoNomeView.Update;
begin
Edit.Text:= TContato(FSubject).Nome;
end;
{ TContatoEnderecoView }
procedure TContatoEnderecoView.OnTextChanged(Sender: TObject);
begin
TContato(FSubject).Endereco := Edit.Text;
end;
procedure TContatoEnderecoView.Update;
begin
Edit.Text := TContato(FSubject).Endereco;
end;
{ TContatoTelefoneView }
procedure TContatoTelefoneView.OnTextChanged(Sender: TObject);
begin
TContato(FSubject).Telefone := MaskEdit.Text;
end;
procedure TContatoTelefoneView.Update;
begin
MaskEdit.Text := TContato(FSubject).Telefone;
end;
{ TContatoNomeLabelView }
procedure TContatoNomeLabelView.Update;
begin
aLabel.Caption := TContato(FSubject).Nome;
end;
Com o núcleo do nosso MGM podemos agora ativa-lo em nosso formulário. No evento onCreate do formulário visto na Figura 4 vamos criar os objetos mediators e relaciona-los aos componentes GUI e ao objeto TContato. Veja Listagem 4.
FContato := TContato.Create;
FContatoNome := TContatoNomeView.Create(EditNome);
FContatoNomeLabel := TContatoNomeLabelView.Create(Label5);
FContatoEndereco := TContatoEnderecoView.Create(EditEndereco);
FContatoTelefone := TContatoTelefoneView.Create(EditTelefone);
FContato.Attach(FContatoNome);
FContato.Attach(FContatoNomeLabel);
FContato.Attach(FContatoEndereco);
FContato.Attach(FContatoTelefone);
Feito isso ao executar o exemplo nossos componentes estarão ligados às propriedades do objeto como se fossem componentes db-aware. O usuário ao digitará nos TEdit e o padrão MGM se encarregará de atualizar o objeto TContato. Se alterarmos via programação alguma propriedade do objeto, essa mudança será refletida nos controles GUI associados a ele.
Conclusão
A utilização do padrão MGM oferece de maneira simples uma forma de se separar os controles de interface dos objetos de negócio. Com isso pode-se criar classes “Mediator View” para as mais diversas tecnologias de GUI, como componentes ASP.NET, IntraWeb, controles VCL e qualquer controle de terceiro que se deseja utilizar.
Claro que bastante código deve ser implementado, mas tendo esse código implementado uma vez, sua reutilização é total, além de se permitir que testes possam ser executados contra as interfaces.