[Delphi] Design Patterns GoF – Observer

Olá, leitores!
De todos os Design Patterns abordados até o momento, talvez o Observer seja um dos mais fáceis, tanto de compreender, quanto de implementar. Veremos que a sua proposta é bem interessante em relação à comunicação entre objetos. Durante o artigo, é possível que vocês lembrem ou identifiquem situações em que este padrão de projeto cairia bem.
Sem mais delongas, apresento-lhes o Observer!

Introdução

A princípio, pela tradução, pode-se imaginar que o objetivo do Observer é propor uma forma de observar um objeto, aguardando que algum evento ocorra. Se é isso que você imaginou, está correto!

O padrão de projeto Observer foi elaborado para permitir que objetos recebam dados ou notificações sem o conhecimento de quem é o objeto emissor. Dessa forma, alcançamos um baixo acoplamento na arquitetura (novamente!), já que fortes dependências não são estabelecidas. A qualquer momento, podemos substituir o emissor ou os receptores sem prejudicar a funcionalidade existente. Pode-se dizer, portanto, que a proposta primária do Observer envolve a recepção de notificações quando determinado evento ocorre em um objeto assistido.

Bom, consigo citar alguns exemplos práticos deste padrão. Uma delas, bem clássica, é o funcionamento do Windows Explorer. Faça um teste: abra três janelas no mesmo diretório e crie um arquivo em uma delas. Em seguida, veja que as outras duas janelas automaticamente exibirão o arquivo criado, sem a necessidade de atualizá-las. Isso ocorre porque o diretório é o objeto “observável”, enquanto as janelas são os objetos “observadores”.

A minha esposa encontrou um exemplo ainda melhor. O YouTube fornece uma funcionalidade de inscrição em canais para que os inscritos recebam notificações de novos vídeos publicados, como se estivessem observando o canal. Neste caso, o canal atua como “observável” e os inscritos são os “observadores”.

Um último exemplo, mais voltado para o âmbito técnico, é o padrão de arquitetura MVC (Model-View-Controller). Geralmente, a camada View comporta-se como observadora das mudanças de comportamento que ocorrem no Controller, resultando em camadas bastante desacopladas e sem dependências explícitas.

Garanto que, de agora em diante, você certamente irá encontrar inúmeras aplicações do Observer por aí. 🙂

Estrutura

Você deve ter notado que escrevi “observável” e “observador” entre aspas. Foi proposital. No contexto do Observer, estes elementos recebem nomes diferentes, nos quais utilizaremos até o final do artigo:

  • Subject: Interface que define a assinatura de métodos das classes que serão observáveis;
  • Concrete Subject: implementação da Interface Subject;
  • Observer: Interface que define a assinatura de métodos das classes que serão observadoras;
  • Concrete Observer: implementação da Interface Observer;

Exemplo de codificação do Observer

Para exemplificar o Observer em um ambiente prático, pensei em um controle financeiro. A ideia é simples: quando uma nova operação financeira for cadastrada, a aplicação terá que atualizar os dados em painéis distintos: o balanço financeiro, os valores de débitos agrupados por categoria e um log do histórico de operações. Cada um destes painéis serão VCL Frames. Optei por este componente justamente para demonstrar a comunicação que ocorre entre objetos que não se referenciam.

Acredito que, só com essas informações, já ficou fácil identificar os elementos, não é? O cadastro de operações atuará como Concrete Subject, enquanto os painéis serão Concrete Observers.

A primeira etapa é modelar uma estrutura que não faz parte do contexto do Observer, mas julgo importante implementá-la. Trata-se de um record que contém atributos para armazenar os dados que serão enviados na notificação, comportando-se como um objeto de “transporte” de dados:

Interface Observer

A segunda etapa é criar a Interface Observer. Veja, a seguir, que há apenas um método, no qual será chamado automaticamente quando houver uma nova notificação:

Classes Concrete Observers

A terceira etapa, talvez, é a mais morosa. Criaremos os Concrete Observers, que serão os VCL Frames, lembrando que cada um deles deverá implementar a Interface acima, implicando, claro, na declaração do método Atualizar. Para que não fique tão extenso, decidi inserir a imagem do frame acompanhada do código-fonte. Além disso, como o exemplo é didático, não me preocupei com strings e números mágicos, ok?

Frame Observer - Balanço Financeiro

Frame Observer - Agrupamento de Débitos

Frame Observer - Log de Operações

Interface Subject

A quarta etapa é codificar a Interface Subject, que deve obrigatoriamente providenciar três métodos essenciais para adicionar, remover e notificar Observers:

Classe Concrete Subject

Para que o exemplo fique ainda mais desacoplado, o Concrete Subject também será um VCL Frame, porém, a codificação é um pouco mais extensa. No Concrete Object, devemos utilizar uma lista de objetos responsável por armazenar os Observers registrados. Os métodos AdicionarObserver e RemoverObserver, portanto, terão a função de manipular essa lista, incluindo ou excluindo os Observers.

Frame Subject - Cadastro de Operações Financeiras

Não terminamos por aí. Codificaremos, por último, o método Notificar, que merece uma atenção. Nele, um objeto do tipo TNotificacao será declarado, preenchido, e enviado para cada Observer registrado na lista:

Este método será chamado quando o botão “Gravar” for acionado (vide imagem acima):

Tudo agora faz sentido. Ao clicar em “Gravar”, o Concrete Subject chamará o método Atualizar de cada Concrete Observer, enviando os dados da operação financeira. Cada um deles receberá a notificação, comportando-se conforme o que estão destinados a processar. Bem interessante, não?

Em ação!

O último passo é juntar tudo!

Em um formulário (Client), adicionei os quatro frames que criamos neste artigo: um Concrete Subject e três Concrete Observers. No evento OnCreate do formulário, montaremos as peças dessa arquitetura, adicionando os três frames Observers na lista do Concrete Subject:

Ao executar o projeto e adicionar algumas operações, observe que todos os frames se comunicam, trabalhando em conjunto, como se estivessem em uma mesma unit:

Exemplo de aplicação utilizando o padrão de projeto Observer

Faço questão de repetir: nenhum deles estão explicitamente referenciados. Na unit do Concrete Subject, não adicionamos a referência dos três frames observadores na seção uses. Do mesmo modo, nas units dos Concrete Observers, não adicionamos a referência do frame observável. Eles não se conhecem, mas se comunicam perfeitamente, resultando em um nível baixíssimo de acoplamento.

Conclusão

Poderíamos ter codificado um mecanismo pra remover os Observers em tempo de execução, porém, demandaria algumas linhas extras de codificação. Este método é conveniente para cenários em que um dos Observers pode decidir não receber mais as notificações. Uma boa analogia são as notificações do Facebook quando algum evento ocorre na linha do tempo (comentários, curtidas ou compartilhamentos). A qualquer momento, o usuário pode desligar as notificações de uma publicação específica, simulando um comportamento do método RemoverObserver.

Enfim, leitores, isso é o que deixo do Observer para vocês. Este padrão de projeto é largamente utilizado em função da sua proposta de desacoplamento e facilidade na propagação de dados. Além disso, claro, vale reforçar a ideia de que os elementos concretos da arquitetura não se conhecem, tornando-a bastante abstrata.

O projeto de exemplo, com algumas melhorias técnicas, está disponível no GitHub:

 

Grande abraço, pessoal!


 

André Celestino