Italo Info


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.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

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:

Diagrama do observer - Temporizador
Diagrama do observer - Temporizador

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!