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.

Como exibir os objetos nos controles visuais?
Figura 1. Como exibir os objetos nos controles visuais?

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.
 
Nota: Existem outros padrões que tratam deste mesmo problema, como o MVC (Model-View-Controller) e MVP (Model-View-Presenter), para cada um deles o termo “View” significa algo diferente. Fazendo uma comparação entre MGM e MVP, uma “View” no MGM equivale a um “Presenter” do MVP.

Na Figura 2 vemos como que se dá o funcionamento do padrão MGM.

Funcionamento do MGM (fonte: andypatterns)
Figura 2. Funcionamento do MGM (fonte: andypatterns)

Implementação

Todo padrão possui seu diagrama de classes, na Figura 3 temos uma visão conceitual do MGM.

Diagrama de classes (fonte: andypatterns)
Figura 3. Diagrama de classes (fonte: andypatterns)

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;
Listagem 1. Classe TContato
Formulário que exibirá um TContato
Figura 4. Formulário que exibirá um TContato

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;
Listagem 2. Unit Patterns.MGM

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;
Listagem 3. Unit BusinessView

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);
Listagem 4. Evento onCreate

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.