Padrão Observer
Olá. Desta vez escrevo sobre um padrão de projeto para programação Orientada a Objetos muito conhecido: O Observer. Esse padrão, quando aplicado ao projeto de software OO, visa o desacoplamento de módulos. Onde, um módulo pode ser responsável por gerar eventos e outro módulo por ser notificado, dadas as ocorrências dos eventos. Claro que, para o outro módulo ser notificado, é necessário que ele se "inscreva", avisando que quer ser notificado com a ocorrência de um determinado evento.
Para entender melhor, podemos associar um dos módulos como um emissor de origem (ou source) de eventos, e um ouvinte/observador (ou Listener). Assim, o módulo interessado em ser notificado, se inscreve no source concreto para ouvir os eventos emitidos por ele.
O Observer pode, inclusive, ser encontrado na API Java/Swing. Onde, por exemplo, temos um componente de interface gráfica (Source) e os listeners, em que se pode implementar a interface listener com seus métodos e adicioná-la ao source para que os métodos implementados sejam executados com a emissão dos eventos. Veja o exemplo abaixo:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ObserverExemplo extends JFrame implements ActionListener {
private final JButton botao;
public ObserverExemplo() {
botao = new JButton( "Clique Me");
botao.addActionListener( this );
Container c = super.getContentPane();
c.setLayout( new FlowLayout() );
c.add( botao );
super.setTitle( "Exemplo de Observer" );
super.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
super.setSize( 300, 100 );
super.setLocationRelativeTo( this );
}
@Override
public void actionPerformed( ActionEvent e ) {
JOptionPane.showMessageDialog( null, "Botão clicado!" );
}
public static void main(String[] args) {
new ObserverExemplo().setVisible( true );
}
}
Perceba no código acima que o componente button é um source que implementa um ou mais listeners. Logo, para adicionar uma função de clique ao botão, basta implementar a interface ActionListener com seu método "actionPerformed" para que o conteúdo desse método seja executado com o clique do botão. Perceba o "addActionListener" que recebe o Ouvinte como parâmetro.
Diagrama do Observer para um temporizador
Agora vamos a representação em diagrama de classes de uma aplicação do observer em um sistema temporizador. Veja abaixo:
Perceba que há o Temporizador que é o source (Origem dos eventos) e que implementa a interface TempoSource e seus métodos. Essa interface Seria opcional. Isto é, seria possível o temporizador ter a relação direta com a interface TempoListener.
A interface TempoListener define o método que deve ser implementado para ser executado quando um evento de tempo for emitido pelo source.
Implementação do temporizador em Java
Vamos ver agora a implementação em Java desta aplicação de temporizador. Veja o source de tempo abaixo:
public interface TempoSource {
public void executa();
public void addTempoListener( TempoListener listener );
}
A interface TempoSource apenas especifica os métodos a serem implementadas pelo emissor de tempo concreto. Isto é, a classe Temporizador. Logo em seguida vem a interface TempoListener:
public interface TempoListener {
public void tempoCompletado( int contador );
}
A interface TempoListener especifica o método que deve ser chamado quando o emissor emitir um evento de tempo completado. Abaixo, o Observador de tempo:
public class TempoObservador implements TempoListener {
@Override
public void tempoCompletado( int contador ) {
System.out.println( "Execução ("+contador+"º)..." );
}
}
A classe TempoObservador implementa o método de TempoListener. O método "tempoCompletado" deve ser executado a cada evento. Perceba também que pode haver mais de um observador. Isto é, cada observador deve ser executado com a ocorrência do evento de tempo completado! Abaixo a implementação da lógica do temporizador:
import java.util.ArrayList;
import java.util.List;
public class Temporizador implements TempoSource {
private final List tempoListeners = new ArrayList();
@Override
public void executa() {
int contador = 0;
while( true ) {
contador++;
for( TempoListener l : tempoListeners )
l.tempoCompletado( contador );
try {
Thread.sleep( 5000 );
} catch ( InterruptedException ex ) {
}
}
}
@Override
public void addTempoListener( TempoListener listener ) {
tempoListeners.add( listener );
}
}
A classe temporizador implementa os métodos da interface TempoSource. Perceba que há um método "addTempoListener" que pode ser chamado externamente à classe, cujos "TemposListeners" adicionados são chamados no método execute a cada ciclo de 5 segundos. Após o incremento do contador, o método "tempoCompletado" de cada listener adicionado é chamado com o atual valor do contador. Isso é o evento de tempo sendo disparado e tratado!
Veja agora como fica a classe principal que une o temporizador e o observador de tempo:
public class Main {
public static void main( String[] args ) {
TempoListener ouvinte = new TempoObservador();
Temporizador temporizador = new Temporizador();
temporizador.addTempoListener( ouvinte );
temporizador.executa();
}
}
Perceba que a lógica sobre o que deve ser executado a cada chamada do temporizador está agora desacoplada do método "executa" da classe Temporizador. Houve um desacoplamento desses módulos! O Temporizador não precisa conhecer sobre a lógica executada pelo Ouvinte. Ele precisa apenas que lhe seja passada uma implementação a ser executada com a ocorrência de um evento.
Considerações finais
O padrão observer tem diversas aplicações. A biblioteca Java/Swing implementa o padrão de projeto observer para o gerenciamento de eventos dos seus componentes de interface gráfica. Aqui, tentei ilustrar a aplicação do padrão observer aplicado a um simples aplicativo temporizador que executa uma ação a cada 5 segundos.
É isso pessoal. Até a próxima!