O padrão de projeto Observer é um dos padrões mais utilizados e aceitos entre os padrões de projetos nas linguagens de programação mais modernas, além de frameworks que lidam principalmente com interfaces gráficas (UI) com o usuário. A API padrão do Java (J2SE) utiliza largamente este padrão, já na plataforma Java EE 7 temos um suporte nativo para que o padrão Observer seja utilizado de forma ainda mais simples e rápida pelos desenvolvedores empresariais, podendo utilizar sem precisar implementá-lo de fato (ou a partir do rascunho como alguns autores afirmam).
No restante do artigo veremos o que é o padrão Observer, como este pode ser implementado na plataforma Java SE e como implementá-lo na nova plataforma Java EE 7 utilizando anotações especialmente projetadas para o padrão.
Padrão Observer
O livro do GoF descreve o padrão Observer da seguinte forma: "o padrão Observer define uma dependência de um para muitos entre os objetos, em que quando tem-se uma alteração no estado de um objeto, todas as dependências são notificadas e atualizadas automaticamente”. O padrão Observer é considerado um padrão de projeto comportamental e tem como base o princípio da herança.
Ele é descrito como um padrão em que possuindo um objeto que tem seu estado alterado, este deve informar aos outros objetos que uma alteração ocorreu. O objeto que alterou o seu estado é chamado de Subject (Sujeito) e os objetos que receberam a notificação da alteração são chamados de Observers (Observadores). Como pode-se notar, o relacionamento é de um para muitos (ou one-to-many), na qual o Subject pode ter muitos Observers.
O Diagrama de classe da Figura 1 mostra mais detalhes sobre o funcionamento do padrão Observer.
Figura 1. Diagrama de classe do Padrão Observer.
O diagrama de classe reforça a teoria já discutida sobre o padrão em que tem-se a presença da interface Subject e da sua classe concreta ConcreteSubject, que define o comportamento dos objetos para se registrarem, através do Attach, e também para serem removidos, através do Detach. Também podemos verificar que o ConcreteSubject é quem implementa a interface Subject e seus métodos e ainda define o seu próprio estado. O método notify() será utilizado para atualizar todos os observadores registrados sempre que o seu estado mudar. Veremos na implementação do padrão que existe um método específico para isso na API do Java. Do outro lado do diagrama tem-se o Observer e o ConcreteObserver, que tem o método update() chamado quando o estado do Subject é alterado. A classe ConcreteObserver implementa a interface Observer, definindo assim o método update(), que também é definido na API do Java conforme ficará mais claro na implementação do padrão.
Um exemplo de implementação do padrão Observer são as aplicações baseadas em chat. Podemos imaginar uma aplicação que automaticamente se atualiza, a cada segundo, para verificar se há novas mensagens disponíveis a partir de outros usuários que também fazem parte do chat. Dessa forma, cada cliente que possui a aplicação de chat deve regularmente verificar com o servidor se há novas mensagens postadas por um dos outros clientes que fazem parte deste. Esta não é uma aplicação muito performática e, dependendo do número de clientes, pode ter sérios problemas de desempenho.
Essa abordagem seria consideravelmente melhor e faria mais sentido se a cada nova mensagem esta pudesse ser enviada para todos os clientes. Certamente essa aplicação obteria um desempenho bastante superior em relação a aplicação anterior. Isso pode ser resolvido basicamente utilizando o Observer, que seria o servidor do chat, enquanto que cada cliente seria o Subject no padrão. O servidor seria registrado com cada cliente, e quando o este postasse uma nova mensagem, em que geraria uma alteração no estado do Subject, ele deveria chamar um método no servidor para notificá-lo de uma nova mensagem. Então o servidor chamaria um método em todos os clientes registrados e enviaria a mensagem para cada um.
Cada implementação concreta do Observer precisa compartilhar uma interface similar. Na implementação do Subject cada Observer se registra no Observer, assim, quando o Subject possui uma alteração, o Observer chama cada Subject registrado para notificar o Observer sobre a alteração. Nos exemplos a seguir será visto como podemos realizar essa implementação, que é muito eficiente, pois apenas uma chamada ocorre para cada Observer no momento da mudança. Por isso diversos frameworks de interface gráfica com o usuário utilizam bastante esse padrão, principalmente aplicações gráficas baseadas em desktop, como os famosos Buttons, componentes Drag and Drop, entre outros. Os frameworks baseados na web também utilizam internamente o padrão Observer.
Implementando o padrão Observer como um POJO
Podemos implementar o padrão de projeto Observer utilizando a plataforma Java SE. Para isso precisamos primeiramente implementar a interface Observer e estender a classe Observable. A interface Observer possui o método update(Observable o, Object arg) que é chamado sempre que o objeto observado é alterado. A classe Observable possui diversos métodos como:
- addObserver(Observer o), que adiciona Observers;
- clearChanged() countObservers(), deleteObserver(Observer o), que deleta um Observer;
- deleteObservers(), que limpa toda lista de Observers;
- hasChanged(), que testa se o objeto foi alterado;
- notifyObservers(), que notifica todos os Observers sobre alterações;
- setChanged(), que marca um Observer como alterado.
No exemplo da Listagem 1 temos uma agência de notícias que informa a seus clientes quando uma nova história é publicada através da implementação da classe AgenciaDeNoticias.
Listagem 1. Implementação da agência de notícias estendendo Observable.
package br.com.devmedia.observer;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
public class AgenciaDeNoticias extends Observable implements Editor {
private List<Observer> canaisComunicacao = new ArrayList<>();
public void adicionaNoticia(String novaNoticia) {
notifyObservers(novaNoticia);
}
public void notifyObservers(String novaNoticia) {
for (Observer observer : this.canaisComunicacao) {
observer.update(this, novaNoticia);
}
}
public void registrar(Observer observer) {
canaisComunicacao.add(observer);
}
}
Nessa classe também implementamos a interface Editor, que tem o seguinte código:
package br.com.devmedia.observer;
public interface Editor {}
O próximo passo agora é criar a classe que observa as alterações na AgenciaDeNoticias. Este observador deve implementar a interface Observer, conforme mostra a Listagem 2.
Listagem 2. Implementação do observador concreto implementando Observer.
package br.com.devmedia.observer;
import java.util.Observable;
import java.util.Observer;
public class Radio implements Observer {
@Override
public void update(Observable agencia, Object novaNoticia) {
if (agencia instanceof Editor) {
System.out.println((String)novaNoticia);
}
}
}
Por fim, basta registrar o observador criado em AgenciaDeNoticias e criar algumas notícias que serão posteriormente divulgadas.
Na Listagem 3 segue um exemplo de utilização do padrão Observer criado anteriormente.
Listagem 3. Implementação de uma classe de teste para testar o padrão Observer.
package br.com.devmedia.observer;
public class TestePadraoObserver {
public static void main(String args) {
// Cria o Observer e o Subject
AgenciaDeNoticias agenciaDeNoticias= new AgenciaDeNoticias();
Radio radio = new Radio();
// Registra o Observer com o Subject
agenciaDeNoticias.registrar(radio);
// Agora adicionamos algumas notícias
agenciaDeNoticias.adicionaNoticia("Grêmio faz goleada histórica no Internacional.");
agenciaDeNoticias.adicionaNoticia("Após humilhação histórica inter demite o seu treinador.");
agenciaDeNoticias.adicionaNoticia("Torcida do internacional invade centro de treinamento após humilhação.");
}
}
A saída após a execução do teste do código apresentado será:
Grêmio faz goleada histórica no Internacional.
Após humilhação histórica inter demite o seu treinador.
Torcida do internacional invade centro de treinamento após humilhação.
Pode-se notar que existe a possibilidade de registrar diversos outros observers com o AgenciaDeNoticias e receber as atualizações conforme as notícias são atualizadas. Um exemplo seria registrar outros dois observers como o CanalTV ou NoticiasInternet. Também é possível ter outros Editores, que por sua vez, implementa Observable e assim poderia haver outros observers que receberiam notícias deste outro editor.
Um pequeno problema que se tem aqui é que como Observable já está sendo estendido não é possível estender outra classe. A pior situação é se já houvesse uma classe sendo estendida e fosse necessária a extensão de Observable. Porém, é possível implementar o padrão Observer sem precisar utilizar a classe Observable.
Implementando o padrão Observer na plataforma Java EE 7
Apesar do Java SE já fornecer suporte ao padrão Observer, a plataforma Java EE oferece uma forma ainda mais fácil de implementar o padrão Observer. Para isso, utilizamos a anotação @Observes e a interface javax.enterprise.event.Event<T>. Assim, qualquer método anotado com @Observes “escuta” por eventos de um certo tipo. Quando este método "ouve" um evento, o parâmetro do método observer recebe uma instância de um determinado tipo e executa o método.
Segue na Listagem 4 um exemplo de um bean que dispara um evento do tipo String e um outro bean que recebe eventos desse tipo.
Listagem 4. Exemplo de implementação de um bean Observable na plataforma Java EE 7.
package br.com.devmedia.observer;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.enterprise.event.Event;
import javax.inject.Inject;
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class ExemploServicoEvento {
@Inject
private String mensagem;
@Inject
Event<String> evento;
public void iniciaServico(){
evento.fire("Iniciando o serviço " + mensagem);
}
}
O container injeta um objeto Event do tipo String na variável de instância evento da classe ExemploServicoEvento. Isso forma parte da mensagem quando o objeto String é acionado.
O Observable está completo, agora é necessário criar um Observer que escuta pelos eventos do tipo String.
A inclusão da anotação @Observes na assinatura do método marca o método como um Observer do tipo que o procede, conforme pode ser visto no exemplo da Listagem 5, em que se tem um tipo String. A anotação @Observes seguida pelo objeto faz toda a mágica necessária para os desenvolvedores, permitindo assim que o método anotado observe o evento acionado do tipo dado.
Segue na Listagem5 o código do Observer que escuta os eventos.
Listagem 5. Exemplo de implementação do bean Observer.
package br.com.devmedia.observer;
import javax.ejb.Stateless;
import javax.enterprise.event.Observes;
@Stateless
public class ExemploBeanObserver {
public void servicoObserver(@Observes String mensagem){
System.out.println("Mensagem do Serviço: " + mensagem);
}
}
O método servicoObserver possui a anotação @Observes na assinatura do método, que faz com que ele seja um Observer para eventos do tipo String. Quando um evento do tipo String ocorre, o método servicoObserver recebe o objeto que o evento produziu através do seu parâmetro. O método então pode manipular este objeto como ele bem desejar, no caso do exemplo apresentado, a mensagem foi exibida no console.
Executando o servidor e invocando o método iniciaServico pode-se verificar toda a mágica acontecendo através da String sendo injetada na classe ExemploServicoEvento, após isso um evento String sendo acionado pelo método servicoObserver da classe ExemploBeanObserver e então a mensagem sendo exibida no console. Surpreendentemente isso é tudo que precisamos para implementar um padrão Observer na plataforma Java EE 7 sem nem mesmo precisar de qualquer tipo de configuração adicional.
Outra situação que podemos implementar é o disparo e observação de outros tipos de objetos da aplicação que seriam observadas por seu tipo. No exemplo da Listagem 6 podemos verificar como é fácil diferenciar os mesmos tipos de objetos de objetos e configurar diferentes observadores para ouvi-los também. Neste exemplo apresentado utilizamos os famosos Qualificadores para distinguir dos objetos Strings.
Listagem 6. Exemplo de como criar um qualificador.
package br.com.devmedia.observer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Qualifier;
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.PARAMETER})
public @interface ExemploEventoMensagem {
Type value();
enum Type{ SERVICO, PARAMETRO }
No código apresentado temos um qualificador ExemploEventoMensagem e dois tipos enum: SERVICO e PARAMETRO que serão utilizados para agir como anotações para marcar as Strings a serem acionadas pelas instâncias de evento.
No exemplo da Listagem 7 vamos alterar o ExemploServicoEvento para comportar os novos tipos.
Listagem 7. Alterando a classe ExemploServicoEvento para comportar os novos tipos.
package br.com.devmedia.observer;
import br.com.devmedia.observer.ExemploEventoMensagem.Type;
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class ExemploServicoEvento {
@Inject
private String mensagem;
@Inject @ExemploEventoMensagem(Type.SERVICO)
Event<String> eventoTipoServico;
@Inject @ExemploEventoMensagem(Type.PARAMETRO)
Event<String> eventoTipoParametro;
public void iniciaServico(){
eventoTipoServico.fire("Iniciando o serviço " + mensagem);
eventoTipoParametro.fire("‐d ‐p"); //passando parâmetros para
//esse tipo de evento
}
Para usar qualificadores apenas adicionamos a anotação ExemploEventoMensagem (que foi criada na última listagem) para a instância a ser injetada com o tipo enum desejado entre parênteses. Então podemos disparar os eventos de dentro do método iniciaServico como realizamos anteriormente.
Agora precisamos adicionar as anotações no Observer, apenas adicionamos os qualificadores para a anotação @Observes, como mostra a Listagem 8.
Listagem 8. Alterando a classe ExemploBeanObserver para comportar os novos tipos.
import br.com.devmedia.observer.ExemploEventoMensagem.Type;
@Stateless
public class ExemploBeanObserver {
public void servicoObserver(@Observes @ExemploEventoMensagem(Type.SERVICO) String mensagem) {
System.out.println("Mensagem do Serviço: " + mensagem);
}
public void parametroObserver(@Observes @ExemploEventoMensagem(Type.PARAMETRO) String mensagem) {
System.out.println("Utilizando parametros: " + mensagem);
}
}
Apenas com essas alterações já é possível disparar e observar nossos próprios objetos.
Outras dicas para o padrão Observer
O padrão de Observer pode proporcionar grandes ganhos de desempenho, também é uma forma eficaz de promover o acoplamento fraco e de alterar a direção entre a chamada e a escuta como pode ser verificado nos exemplos iniciais. Como regra geral para utilização do padrão de projeto Observer, sempre que um recurso Subject alterar e o seus chamadores tentaremos captura essas informações do Subject, nunca devemos hesitar em utilizar o padrão Observer.
Ao projetar uma aplicação ou refatorar um código é sempre interessante verificar o tempo que está sendo gasto por um determinado método e analisar se é possível implementar o padrão Observer a fim de melhorar o desempenho. Caso seja necessário migrar um código para o padrão Observer na plataforma Java EE a partir de um código puro na plataforma Java SE isso é feito de forma bastante rápida e simples. A migração de código não deve ser um empecilho para implementar o padrão Observer.
Como pode-se observar o padrão Observer tem uma fraqueza que é o seu desacoplamento, que pode acarretar em perda de controle e também em dificuldades para realizar o debug da aplicação. Pode-se concluir que a maior força do padrão (exatamente o desacoplamento) pode acabar como uma grande fraqueza, por isso é recomendado que a implementação se mantenha o mais simples possível, porém caso a situação esteja indo para um lado mais complexo é interessante repensar o código ou até mesmo a aplicação. Também é interessante evitar múltiplas camadas de Observers, utilizando apenas uma ou algumas é o ideal.
Outra dica bastante importante é que o nome de um observador reflita a sua finalidade. Além disso, devemos incorporar a razão para a observação no nome dos seus métodos, expressando assim a finalidade da classe.
Bibliografia
[1] Erich Gamma, Ricard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994).
[2] Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra. Head First Design Patterns. O'Reilly Media, 2004.
[3] Murat Yener, Alex Theedom. Proffesional Java EE Design Patterns. Wrox, 2015.
[4] Documentação Oficial
da Oracle - Observer (Java Platform 7)
http://docs.oracle.com/javase/7/docs/api/java/util/Observer.html
[5] Documentação Oficial
da Oracle - Observable (Java Platform 7)
http://docs.oracle.com/javase/7/docs/api/java/util/Observable.html