Padrão Observer

 

Muitas vezes, nos mais diversos sistemas precisamos implementar algum comportamento que depende de um específico fator. Quando esse desenvolvimento é feito através de objetos desejamos que os objetos envolvidos estejam o mínimo possível interligados, aplicando a regra de “baixo acoplamento”, ou seja, quanto menos os objetos souberem um do outro, melhor.

Uma situação real

Temos um sistema de e-commerce que ao ser feita uma venda, o cliente que comprou o produto recebe um e-mail com uma confirmação, o setor de logística é notificado,  através do envio automatizado de um arquivo texto, do local de entrega do produto.

A maneira convencional

Como todo desenvolvedor RAD, de primeira mão já vemos tudo resumido em uma procedure que será disparada no evento onClick do botão “Confirmar Compra” gerando um algoritmo parecido com o da Listagem 1.

 

Listagem 1.  Algoritmo RAD do evento OnClick do botão “Confirmar Compra”

 

1 – Validar comprar

2 – Se a compra não é válida pára o processamento e notifica usuário

3 – Se for válida, notifica usuário e envia arquivo de texto

 

O principal problema que vejo nessa abordagem é de que um requisito da nossa regra de negócios está implementado na camada de apresentação do sistema. Qualquer mudança nessa área pode acarretar erros nas regras de negócio ou implicar em duplicação de código. Hoje nosso sistema é acessado através de uma página. E se amanhã tivermos que disponibilizar essa mesma rotina para algum parceiro que gostaria de integrar nossa rotina de vendas em seu sistema interno?  Eu faria isso através de web services, e quanto aquele código que fizemos no botão confirmar venda da página? Teríamos que replicá-lo no web service?

O desenvolvimento eficaz

Muitos desenvolvedores Delphi se prendem muito ao RAD, criando aplicações de maneira eficaz e não eficiente. Para entender melhor essa diferença vamos a um exemplo do cotidiano.

Eu possuo um aquário de quase 100 litros em casa, muito bonito. Minha esposa chega então, na hora do almoço, com um novo peixe.  Como é quase hora de sairmos para almoçar eu simplesmente pego o saco plástico com o peixe e o coloco boiando sobre a água do aquário, pra que a temperatura dos dois se iguale. Passado 10 minutos, retiro o peixe do saco plástico e o coloco no aquário.

Agi de forma eficaz, porque em 10 minutos transferi o peixe pro meu aquário sem estourar meu limite de tempo pro almoço e pensei no bem estar do peixe.

Agora vamos aos fatos. Não é apenas a diferença de temperatura da água que pode fazer mal ao peixe, mas também a diferença de PH, devemos fazer o peixe se habituar, de forma rápida, à nova água em que será introduzido.

A forma eficiente seria acrescentar à água que ele veio, em intervalos de 10 minutos, a água do meu aquário até que se triplique a quantidade de água. Ou seja, eu levaria no mínimo 20 minutos para fazer isso, mas teria a certeza do bem estar do novo peixe.

Voltando para a programação, a maneira eficiente seria isolar essas regras de negócio em classes de maneira tal que a camada de apresentação (web services, formulários desktop, páginas web, etc.) pudesse variar sem interferi-las.

Mas por falta de tempo, prática, ou até mesmo conhecimento, aplicamos a forma eficaz, que não está errada, mas deixa a desejar.

 

Sobre separar a camada de regras de negócio da camada visual, fica para um próximo artigo pois o assunto é muito extenso.

Mudança de pensamento

Quero apresentar hoje o padrão Observer. Ele é indicado para tratar situações de dependência como a mencionada no inicio do artigo. Sua utilização oferece o benefício de se relacionar duas ou mais classes sem criar um vínculo explícito entre elas.

Abaixo na Figura 1 temos a visão formal, um diagrama de classes UML, do padrão Observer.

 

imagem 

Figura 1. Padrão Observer.

 

O padrão Observer abstrai a relação dos objetos definindo-os como Observer e Subject.

Observer são objetos que ficam aguardando por uma mudança no estado de determinado objetos para poderem tomar uma ação. Subject são os objetos que estão sendo observados.

Olhando para nossa situação, o objeto Venda é um Subject e a notificação ao usuário e envio de arquivo são os Observers.

Vamos colocar logo a mão na massa e implementá-lo para resolver nosso problema. Primeiramente vamos criar as duas classes base que representam o padrão em si, TSubject e TObserver, veja Listagem 2

 

Listagem 2. Classes TObserver e TSubject

  TObserver = class

  public

    FSubject: TSubject;

    procedure Update; virtual; abstract;

  end;

 

  TSubject = class

  public

    FObservers: TObjectList;

    procedure Attach(const Observer: TObserver);

    procedure Detach(const Observer: TObserver);

    procedure DetachAll;

    procedure Notify;

  end;

 

E na Listagem 3 vemos como implementar nosso Subject

 

Listagem 3. Implementação do Subject

procedure TSubject.Attach(const Observer: TObserver);

begin

  if FObservers = nil then

    FObservers := TObjectList.Create;

 

  Observer.FSubject := Self;

  fObservers.Add(Observer);

end;

 

 

procedure TSubject.Detach(const Observer: TObserver);

begin

  if fObservers <> nil then

  begin

    fObservers.Remove(Observer);

    if FObservers.Count = 0 then

      FObservers := nil;

  end;

end;

 

procedure TSubject.DetachAll;

var

  i, k: Integer;

begin

  if FObservers <> nil then begin

    k := FObservers.Count;

    for i := Pred(k) downto 0 do

      Detach(FObservers[i] as TObserver);

  end;

end;

 

procedure TSubject.Notify;

var

  i: Integer;

begin

  if FObservers <> nil then

    for i := 0 to FObservers.Count -1 do

      (fObservers[i] as TObserver).Update;

 

end;

 

Temos agora que implementar nossas classes de negócio que terão o comportamento do padrão observer, veja na listagem 4.

 

Listagem 4.  TObserver e TSubject implementados

  TVenda = class(TSubject)

  private

    FCliente: TCliente;

    FProduto: TProduto;

    FDataVenda: TDateTime;

    FQtdeVendida: integer;

    function GetConfirmacao: string;

    function GetDadosEntrega: string;

    procedure SetCliente(const Value: TCliente);

    procedure SetProduto(const Value: TProduto);

    procedure SetDataVenda(const Value: TDateTime);

    procedure SetQtdeVendida(const Value: integer);

  public

    constructor Create;

    destructor Destroy; override;

    property Cliente: TCliente read FCliente write SetCliente;

    property Produto: TProduto read FProduto write SetProduto;

    property DataVenda: TDateTime read FDataVenda write SetDataVenda;

    property QtdeVendida: integer read FQtdeVendida write SetQtdeVendida;

    procedure Finalizar;

  end;

 

  TLogistica = class(TObserver)

  public

    procedure Update; override;

  end;

 

  TConfirmacao = class(TObserver)

  public

    procedure Update; override;

  end;

 

O método Finalizar de TVenda, Listagem 5, chama o método herdado Notify que varre a lista de observers notificando a cada um que a compra foi finalizada.

 

Listagem 5. Método que chama o Notify

procedure TVenda.Finalizar;

begin

  {

   Aqui pode se validar a venda, e esta estando ok,

   notifica-se os observers, sejam quem forem

  }

  Notify;

end;

 

Posteriormente cada classe que implementa TObserver executa o método Update à sua maneira, conforme listagem 6.

 

Listagem 6. Método Update

{ TLogistica }

 

procedure TLogistica.Update;

begin

  if FSubject is TVenda then ShowMessage(TVenda(FSubject).GetDadosEntrega);

end;

 

{ TConfirmacao }

 

procedure TConfirmacao.Update;

begin

  if FSubject is TVenda then ShowMessage(TVenda(FSubject).GetConfirmacao);

end;           

 

Com essas classes implementadas, mais TCliente e TProduto, temos o que é chamado de Business Object Model, ou, Modelo de objetos de negócio. Nossas regras estão encapsuladas em classes, de tal forma que podemos variar a interface com o usuário.

Na Listagem 7 temos parte do código do exemplo que está anexado.

 

Listagem 7. Utilizando observer

procedure TForm1.FormCreate(Sender: TObject);

begin

{

  Preparando a venda...

 

  O preenchimento das classes FCliente e FProduto

  poderiam se dar de qualquer forma, como por um

  formulário comum, uma página Web ou até mesmo

  por um webservice

}

 

  FCliente := TCliente.Create;

  FCliente.Nome := 'Paulo Roberto Quicoli';

  FCliente.Endereco := 'Av. do Centro, S/N';

  FCliente.Email := 'pauloquicoli@gmail.com';

 

  FProduto := TProduto.Create;

  FProduto.Nome := 'TV de Plasma 42"';

  FProduto.Valor := 7899.00;

 

  FAvisaUsuario := TConfirmacao.Create;

  FAvisaLogistica := TLogistica.Create;

 

  FVenda := TVenda.Create;

{

  Veja que aqui adicionamos os observers

  que estão esperando pela conclusão da venda

}

  FVenda.Attach(FAvisaUsuario);

  FVenda.Attach(FAvisaLogistica);

end;

 

 

procedure TForm1.btFinalizarClick(Sender: TObject);

begin

{

  Aqui finalizamos a compra, mais uma vez digo

  que isso poderia se dar por um formulário comum,

  uma página web ou webservice

}

  FVenda.Cliente := FCliente;

  FVenda.Produto := FProduto;

  FVenda.DataVenda := now;

  FVenda.QtdeVendida := 1;

  FVenda.Finalizar;

end;

Conclusão

Encapsular nossas regras de negócio em classes é melhor coisa que podemos fazer para nossos projetos. Veja na Listagem 7, no código do evento btFinalizarClick não mencionamos a regra exigida, que era notificar usuário e enviar um arquivo para a logística. Tudo ficou no nosso modelo de negócios que, desenvolvido da forma sugerida, poderá ser reutilizado por outras interfaces.

A implementação do padrão Observer foi a mais simples possível, juntamente com o exemplo, para que a idéia do seu funcionamento pudesse ser assimilada de forma fácil.

Abraço e até mais!