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.
Vamos definir as abstrações, chamaremos de Operário a interface do operário e Sirene a interface da sirene como mostrado abaixo:
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:
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/