terça-feira, 5 de junho de 2012

Observer - Design Patterns

Design Patterns Observer

Neste artigo abordaremos a modelagem e prática de um padrão de projeto (Design Patterns) bastante importante do GoF (Gang of Four) que é o Padrão Observer.

A essência desse padrão está na possibilidade de uma classe poder fazer uma 'notificação' a um conjunto de classes associadas de que o Estado (Conjunto de Atributos) dela foi alterado por algum motivo. A definição do padrão observer descrita pelos criadores é a seguinte:
"Definir uma dependência um-para-muitos entre objetos para que quando um objeto mudar de estado, todos os seus dependentes sejam notificados e atualizados automaticamente." [GoF]
Você deve estar pensando "Eu posso fazer essa notificação manualmente ou até mesmo utilizando eventos como actionPerformed para fazer a notificação através de chamas a métodos de cada instância (ufaa)". Porém dessa maneira você estará criado um sistema com alto acoplamento e de difícil manutenabilidade.
Utilizando o padrão Observer conseguimos reduzir o uso do relacionamento bidirecional por meio de interfaces e classes abstratas, separando a abstração para ter um alto nível de coesão e baixo acoplamento.
Para ficar mais intuitivo vamos fazer o uso de exemplos. Imagine um operário de obras que esta trabalhando em uma construção junto com seus 'companheiros'.. O fato de uma sirene tocar implica em uma mudança (mudança de estado), que pode ser um sinal para a hora do almoço, pode ser o final do expediente e etc.
Temos então o seguinte ambiente, o objeto observável (Sirene) e os observadores (Operários). Quando o objeto observável alterar o seu estado, enviará um sinal sonoro (mensagem) alertando os objetos observadores.
Padrões de Projeto - Observer








Vamos definir as abstrações, chamaremos de Operário a interface do operário e Sirene a interface da sirene como mostrado abaixo:
Padrões de Projeto - Observer Interface
Perceba que as duas abstrações se relacionam entre si, relacionamento entre interfaces, ou seja, estamos deixando nossa aplicação mais independente separando-as das classes concretas
Na interface Sirene nós especificamos parte do que ela poderá fazer, no caso adicionar ou retirar um observador, da mesma maneira seguiremos com o Operário, sendo que, no mais alto nível de abstração, o mínimo que um observador irá sofrer é o fato de ser notificado (atualizar). Se o operário vai ou não responder ao chamado não cabe a interface.
Com as interfaces em mãos podemos montar nosso diagrama, criaremos duas classes que vão implementar essas interfaces, sendo elas SireneConcreta e OperarioConcreto, conforme vemos abaixo:
Diagrama de Classe Observer
Na classe SireneConcreta criaremos todos os outros métodos necessários para o bom funcionamento da sirene e o método notificarObservadores que ira "varrer" o ArrayList que contem uma lista de observadores.
A classe concreta não foge a regra, irá implementar além do método atualizar todos os métodos dos operários.
Tendo toda a estrutura diagramatizada você poderá utilizar o próprio programa de modelagem (se estiver disponível) para gerar o código da nossa estrutura. Eu estou utilizando para esse fim o Enterprise Architect, porém qualquer um com suporte a UML 2.0 (de preferência) poderá ser usado, seja Netbeans, ArgoUML, ou plugins para eclipse..
Implementando nossas interfaces, teremos,
Interface Sirene:
 
public interface Sirene {
 
 public void adicionarObservador( Operario o );
 public void removerObservador( Operario o );
 
}
 
Interface Operario:
 
public interface Operario {
 
 public void atualizar(Sirene s);
 
}
 
Com as nossas abstrações prontas, podemos dar inicio a construção das classes concretas, teremos,
Classe SireneConcreta:
 
import java.util.ArrayList;
import java.util.Iterator;
 
public class SireneConcreta implements Sirene {
 
 private Boolean alertaSonoro = false;
 private ArrayList observadores = new ArrayList();
 
 public void alterarAlerta(){
  if(alertaSonoro)
   alertaSonoro = false;
  else
   alertaSonoro = true;
  notificarObservadores();
 }
 
 public Boolean getAlerta(){
  return alertaSonoro;
 }
 
 public void adicionarObservador(Operario o) {
  observadores.add(o);
 }
 
 public void removerObservador(Operario o) {
  observadores.remove(o);
 }
 
 private void notificarObservadores(){
  Iterator i = observadores.iterator();
  while(i.hasNext()){
   Operario o = (Operario) i.next();
   o.update(this);
  }
 }
}
 
Perceba que chamamos o método notificarObservadores exatamente quando o objeto SireneConcreta altera seu estado. Na implementação do método notificarObservadores nos fazemos uma varredura simples e informando a cada instância dele mesmo que o objeto que estava sendo observado mudou seu estado. Repare que estamos trabalhando sempre com as Interfaces ao invés das classes concretas.
Classe OperarioConcreto:
 
public class OperarioConcreto implements Operario {
 
 private SireneConcreta objetoObservado;
 
 public OperarioConcreto(SireneConcreta o){
  this.objetoObservado = o;
  objetoObservado.adicionarObservador(this);
 }
 
 public void atualizar(Sirene s) {
  if(s == objetoObservado){
   System.out.println("[INFO] A Sirene mudou seu
estado para: " + objetoObservado.getAlerta());
  }
 }
}
 
Com o construtor dessa maneira podemos ao passo de criar o observador e já setar como parâmetro o objeto observado, logo abaixo podemos chamar o método adicionarObservador que passa por referência a sua própria instância!. Interessante, não?
Quando o método atualizar é chamado nós precisamos checar se a sirene que alterou o estado é a mesma que estamos observando.
Com toda estrutura implementada é hora de testar, para tal vamos criar a classe GerenciadorSirene:
 
public class GerenciadorSirene {
 
 public static void main(String[] args) {
 
  SireneConcreta sirene = new SireneConcreta();
  // Sirete ja começa com valor default false
 
  OperarioConcreto obs1 = new OperarioConcreto(sirene);
  OperarioConcreto obs2 = new OperarioConcreto(sirene);
  // Já passando a sirene como parametro
 
  sirene.alterarAlerta();
  // Nesse momento é chamado o método atualizar
  // das instâncias obs1 e obs2, saída:
  // [INFO] A Sirene mudou seu estado para: true
  // [INFO] A Sirene mudou seu estado para: true
 
  sirene.alterarAlerta();
  //[INFO] A Sirene mudou seu estado para: false
  //[INFO] A Sirene mudou seu estado para: false
 
  // Obs: 2 saídas porque temos 2 observadores
 }
}
 
É possível dar nomes aos observadores criando um atributo String dentro de OperatorConcreto e passando seu nome para o construtor como segundo parâmetro, para ficar mais fácil diferenciá-los.

Artigo original de: http://blog.rafaelcapucho.com/

2 comentários: