O padrão Observer, um dos mais usados no JDK, é responsável por manter a dependência um-para-muitos entre objetos distintos, que serão notificados da mudança de estado do objeto observado.

Na maioria dos exemplos encontrados na web, o padrão Observer raramente é encontrado aplicado juntamente com componentes Swing.

O objetivo deste artigo é demonstrar uma aplicação, do padrão Observer, em uma interface gráfica utilizando os componentes do pacote javax.swing. Para que a notificação de objetos seja explícita, utilizar-se-á uma situação que será descrita nos próximos parágrafos.

Partindo de uma situação

Os taxistas de uma determinada região resolveram informatizar o sistema de chamadas via telefone da seguinte maneira: a telefonista recebe um pedido de taxi e digita os dados do cliente na central. Ao enviar o pedido, todos os taxis (munidos de um computador) receberão uma notificação com os dados.

Como citada no início do artigo, o padrão Observer mantém uma dependência um-para-muitos entre objetos. Na situação descrita não é muito difícil imaginar quem é o “um” e quem são os “muitos”.

Ao ter-se a Central como objeto único e responsável por notificar os taxis de que há um novo cliente disponível, ele será o observado. Os taxis serão, então, os observadores. A Figura 1 fornece uma visualização para o caso.

asposfig01.jpg
Figura 1 – relação um-para-muitos entre central e os taxis

A partir de agora, ao invés de considerá-los observada e observadores, eles serão chamados de Subject e Observers, respectivamente.

Modelando a estrutura Observer

Há diversas maneiras de se implementar o padrão Observer. Neste artigo irá se criar duas interfaces (que serão implementadas pelos objetos concretos): Subject e Observer.

Antes de mais nada, será criada a classe Cliente, como na Listagem 01.


package cliente;

private String nome;      
private String rua;       
private int numero;       
private String bairro;    
private String complemento;        
private String destino;

public Cliente(String nome, String rua, int numero, 
String bairro, String complemento, String destino){
      this.nome = nome;
      this.rua = rua;
      this.numero = numero;
      this.bairro = bairro;
      this.complemento = complemento;
      this.destino = destino;
}
      
public Cliente(){}
      
//métodos de acesso...

public String getEndereco(){
      return rua + ", " + numero + " - " + bairro + " " + complemento;
}
Listagem 01 – Classe Cliente

Agora serão criadas as interfaces que ao serem implementadas pelas classes concretas irão definir o padrão Observer na aplicação.


package patternobserver;

import cliente.Cliente;

public interface Subject {
            public void addObserver(Observer o);
     public void removeObserver(Observer o);
     public void notifyObservers(Cliente corrida);
}
Listagem 02 – Interface Subject (Observada)

package patternobserver;
  
 import cliente.Cliente;
  
 public interface Observer {
         public void update(Cliente corrida);
 }
Listagem 03 – Interface Observer (Observadores)

Uma classe que implemente Subject (Listagem 02), deve fornecer implementações para addObserver que adiciona um observador à lista de observadores, removeObservers que remove um observador da lista e notifyObservers que é responsável por notificar um ou mais observadores.

Esse último método irá receber um Cliente e enviá-lo aos observadores desejados que irão interpretá-lo cada um à sua maneira.

As classes que implementam Observer deverão apenas implementar update que recebe um Cliente (de notifyObservers) e interpreta-o da maneira como desejar, seja exibindo-o, descartando-o, etc. O importante é notar um princípio básico de design que é sempre procurar uma fraca ligação entre os objetos que interagem no mesmo. Logo, é possível perceber que a classe concreta que implementa Subject não precisam saber o que os observadores irão fazer com o Cliente. Apenas os notificam.

Com as interfaces criadas falta apenas criar as classes concretas; isto é, a Central que implementará Subject e a classe Taxi que implementará Observer.

Criando as classes concretas

A Listagem 04 representa a classe CentralComponent (Subject – observada). Como o objetivo principal do artigo é demonstrar o padrão em uma aplicação Swing, essa classe foi criada já pronta para ser exibida em um internal frame.

Não é recomendável dar mais de uma funcionalidade a uma classe – neste caso, a de exibir um formulário e comunicar os observadores – porém, isto foi feito para manter a clareza e resumir o máximo possível a quantidade de código e poder focar o padrão.


package components;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.Iterator;

import javax.swing.JInternalFrame;
import javax.swing.JPanel;

import org.swingBean.actions.ActionChainFactory;
import org.swingBean.actions.ApplicationAction;
import org.swingBean.actions.ValidationAction;
import org.swingBean.descriptor.GenericFieldDescriptor;
import org.swingBean.descriptor.XMLDescriptorFactory;
import org.swingBean.gui.JActButton;
import org.swingBean.gui.JBeanPanel;

import cliente.Cliente;

import patternobserver.Observer;
import patternobserver.Subject;

public class CentralComponent extends JInternalFrame implements Subject{
     private ArrayList observers = new ArrayList();
     
     public CentralComponent(){
            super("Central *Subject*");
            initComponents();
     }
     
     public void initComponents(){
            GenericFieldDescriptor descriptor = XMLDescriptorFactory
            .getFieldDescriptor(Cliente.class, "/ClienteForm.xml", "ClienteForm");
            final JBeanPanel panel = new JBeanPanel(Cliente.class, 
            "Nova Solicitação:", descriptor);
            panel.setPreferredSize(new Dimension(300,180));
            
            ApplicationAction validationActions = ActionChainFactory.createChainActions(
                new ValidationAction(panel), new ApplicationAction(){
                    public void execute() {
                           Cliente cliente = new Cliente();
                           panel.populateBean(cliente);
                           notifyObservers(cliente);
                    }
                });
            JActButton btnEnviar = new JActButton("Solicitar>>>", validationActions); 
            
            JActButton btnLimpar = new JActButton("Limpar", new ApplicationAction(){
                    public void execute() {
                            panel.cleanForm();
                    }
            });
            
            JPanel panelButtons = new JPanel();
            panelButtons.add(btnLimpar);
            panelButtons.add(btnEnviar);
                            
            super.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
            super.setLayout(new BorderLayout());
            super.add(panel, BorderLayout.NORTH);
            super.add(panelButtons, BorderLayout.SOUTH);
            super.pack();
            super.setVisible(true);
     }

     public void addObserver(Observer o) {
            observers.add(o);
     }

     public void removeObserver(Observer o) {
            observers.remove(o);
     }

     public void notifyObservers(Cliente corrida) {
            Iterator it = observers.iterator();
            while(it.hasNext()){
                    Observer observer = it.next();
                    observer.update(corrida);
            }
     }
}
Listagem 04 – classe CentralComponent

Para montagem do formulário de preenchimento dos dados do Cliente foi utilizado o framework SwingBean disponível em http://swingbean.sourceforge.net/. O método initComponents faz a montagem do mesmo, criando o painel através de um arquivo XML, como indica a Listagem 05.


<beanDescriptor>
    <line>
           <property name='nome' label='Nome:' mandatory='true'/>
    </line>
    <line>
           <property name='rua' label='Rua:' colspan='4' mandatory='true'/>
           <property name='numero' label='Nº:' colspan='1' mandatory='true'/>
    </line>
    <line>
            <property name='bairro' label='Bairro:' mandatory='true'/>
           <property name='complemento' label='Complemento:' mandatory='true'/>
    </line>
    <line>
           <property name='destino' label='Destino:' mandatory='true'/>
    </line>
</beanDescriptor>
Listagem 05 – ClienteForm.xml

Os métodos addObserver, removeObserver e notifyObservers de CentralComponent foram implementados de Subject. Os dois primeiros realizam as ações de adição e remoção de observadores do ArrayList observers, declarado no início da Listagem 04. Os observadores, neste caso, são as classes que implementam Observer.

Ainda na Listagem 04, há a criação de um evento chamado de validationActions ( evento do botão btnEnviar) que cria uma instância vazia de Cliente e em seguida chama o método populateBean para setar as informações preenchidas nos atributos de cliente. Se houver algum dado obrigatório não preenchido, o próprio framework se encarrega de exibir uma mensagem de alerta para o usuário. Com o cliente corretamente preenchido, o evento finalmente chama notifyObservers, e é aí que todas classes que implementam Observer serão notificadas.

Com a CentralComponent já pronta, parte-se para a criação da classe TaxiComponent, que serão os taxis observadores (implementam Observer).


package components;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;

import javax.swing.BorderFactory;
import javax.swing.JInternalFrame;
import javax.swing.JPanel;

import cliente.Cliente;

import patternobserver.Observer;

public class TaxiComponent extends JInternalFrame implements Observer{
     private ClienteComponent clienteComponent;
     
     private JPanel panelCliente;
     
     private static int codigo = 1;
     
     public TaxiComponent(){
            super("Taxi *Observer* - #000" + codigo++, true);
            this.clienteComponent = new ClienteComponent(null);
     }
     
     public void initComponents(){
            this.panelCliente = new JPanel(new BorderLayout());
            panelCliente.setBorder(BorderFactory.createTitledBorder(null, "Cliente:"));
            panelCliente.add(clienteComponent, BorderLayout.WEST);
            panelCliente.setPreferredSize(new Dimension(250,100));
            super.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
            super.setLayout(new FlowLayout());
            super.setLocation(320, 0);
            super.add(panelCliente);
            super.pack();
            super.setVisible(true);
     }
     
     private void atualizaCliente(Cliente cliente){
            clienteComponent.setCorrida(cliente);
     }

     public void update(Cliente cliente) {
            atualizaCliente(cliente);             
     }       
}
Listagem 06 – classe TaxiComponent

No início do código é declarada uma instância de ClienteComponent. A Listagem 07 demonstra a criação desta classe que é apenas um painel onde são exibidos os dados do cliente.


package components;

import java.awt.Color;

import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;

import cliente.Cliente;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;

public class ClienteComponent extends JPanel{
     
     private JLabel lNome;
     private JLabel lEndereco;
     private JLabel lDestino;
     
     public ClienteComponent(Cliente cliente){
            lNome = createJLabel("");
            lEndereco = createJLabel("");
            lDestino = createJLabel("");
            if(cliente != null){
                    this.setCliente(cliente);
            }
            this.initComponents();
     }
     
     private void initComponents(){
            CellConstraints cc = new CellConstraints();
            FormLayout layout = new FormLayout("3dlu, pref, 3dlu, 
            pref, 3dlu","3dlu, pref, 3dlu, pref, 3dlu, pref, 3dlu");
            super.setLayout(layout);
            super.add(createJLabel("Nome:"), cc.xy(2, 2));
            super.add(createJLabel("Endereço:"), cc.xy(2, 4));
            super.add(createJLabel("Destino:"), cc.xy(2, 6));
            super.add(lNome, cc.xy(4, 2));
            super.add(lEndereco, cc.xy(4, 4));
            super.add(lDestino, cc.xy(4, 6));
     }
     
     public void setCliente(Cliente cliente){
            this.lNome.setText(cliente.getNome());
            this.lEndereco.setText(cliente.getEndereco());
            this.lDestino.setText(cliente.getDestino());
     }
     
     private JLabel createJLabel(String text){
            JLabel jLabel = new JLabel(text);
            jLabel.setBackground(Color.WHITE);
            jLabel.setBorder(BorderFactory.createLineBorder(Color.BLUE, 1));
            jLabel.setOpaque(true);
            return jLabel;
     }
}
Listagem 07 – classe ClienteComponent

Para configuração da interface gráfica de ClienteComponent, foi utilizada a biblioteca forms-1.0.7 de JGoodies (http://www.jgoodies.com/). O método initComponents faz uso então da biblioteca citada pra posicionar os labels que exibem os dados. O método setCliente atualiza os dados exibidos.

ClienteComponent tem apenas a função de, ao receber um Cliente, exibir os dados em um Painel. Enquanto isso, seu método setCliente atualiza os dados de acordo com o novo Cliente enviado.

Com as classes CentralComponent, TaxiComponent e ClienteComponent, é possível analisar a rotina do padrão observer na aplicação:

  • o responsável por receber a ligação de um Cliente preenche os dados no formulário de CentralComponent;
  • ao clicar no botão “Solicitar”, os dados preenchidos irão montar um objeto de Cliente que, em seguida, será enviado através do método notifyObservers aos observadores cadastrados;
    Nota: Repare que para cada instância criada de TaxiComponent ou de alguma classe que implemente Observer, deverá haver uma chamada em addObserver para que a mesma esteja cadastrada na lista de observadores e ser notificada junto com as outras.
  • Todos os observadores cadastrados receberam a instância de Cliente enviada pela classe observada e cada um agora executa as operações que forem necessárias com o mesmo. No exemplo atual, os computadores de cada taxi irão apenas notificar o taxista da nova chamada.

Para exemplificar o processo, nos próximos parágrafos, será criada uma mini-aplicação onde é possível visualizar a CentralComponent e os TaxiComponent cadastrados atualizando suas exibições a cada nova chamada de Cliente.

Aplicação de exemplo

A Listagem 08 representa o código-fonte completo da aplicação.


package gui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.UIManager;

import components.CentralComponent;
import components.TaxiComponent;

public class GUI extends JFrame{
     private JDesktopPane desktop = new JDesktopPane();
     
     private CentralComponent central;
     
     public GUI(){
            super("Sistema de Gerência de Taxis");
     }
     
     public void initComponents(){
            this.central = new CentralComponent();
            desktop.add(central);
            
            super.setJMenuBar(montaMenu());
            super.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            super.setExtendedState(JFrame.MAXIMIZED_BOTH);
            super.add(desktop);
            super.setVisible(true);
     }
     
     private JMenuBar montaMenu(){
            JMenuBar menuBar = new JMenuBar();
            JMenu mnuObservers = new JMenu("Observers");
            JMenuItem mniAdicionar = new JMenuItem("Adicionar");
            mniAdicionar.addActionListener(new ActionListener(){
                    public void actionPerformed(ActionEvent e){
                            adicionaTaxi();
                    }
            });
            mnuObservers.add(mniAdicionar);
            menuBar.add(mnuObservers);
     
            return menuBar;
     }
     
     private void adicionaTaxi(){
            TaxiComponent taxi = new TaxiComponent();
            taxi.initComponents();
            desktop.add(taxi);
            central.addObserver(taxi);
     }

public static void main(String args[]){
             try{    
               UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch(Exception e){}
            
            GUI gui = new GUI();
            gui.initComponents();
     }
}
Listagem 08 – classe GUI

A classe GUI extende JFrame (javax.swing), e inicialmente declara uma instância de JDesktopPane, do mesmo pacote, e uma CentralComponent. A JDesktopPane criada tem a função de exibir um painel onde serão exibidos a CentralComponent e os TaxiComponent de maneira que se possa posicioná-los da maneira como o usuário quiser, facilitando a visualização das chamadas.

Em initComponents, a central é adicionada e exibida, assim como a interface gráfica geral. Ainda dentro desse método, há uma chamada para montaMenu que irá retornar um JMenuBar contendo apenas um item que é o de adicionar taxis.

O método adicionaTaxi é chamado pelo único item de menu da GUI apresentada, e cria um novo componente visual de taxi (TaxiComponent), inicia sua interface gráfica (initComponents), adiciona o taxi no desktop que é o componente onde serão visualizados pelo usuário e em seguida chama addObserver da central que cadastra o novo Taxi na lista de observadores.

asposfig02.jpg
Figura 2 – interface gráfica do Sistema de Gerência de Taxis

Após criar alguns taxis e posicioná-los na tela, como indica a Figura 2, o usuário já pode realizar as solicitações de taxi através da Central e ao clicar em “Solicitar”, cada um dos Taxis criados irá exibir as informações preenchidas. Isso acontece porque todos eles estão cadastrados na lista de observadores (contida na Central).

Na aplicação não foi tratada a ação de remover um taxi, mas pode ser facilmente tratada, pois a interface Subject já fez com que CentralComponent implementasse essa ação.

Conclusão

No artigo foram usadas interfaces criadas para a aplicação do Sistema de Gerência de Taxis para facilitar a compreensão do padrão. Porém o Java já fornece em suas API classes que suportam o padrão Observer, como as classes Observable e Observer, ambas de java.util, e possuem mais recursos.

Uma das vantagens mais explícitas do padrão é o fato das ligações entre observador e observável serem fracas, o que é um bom princípio de design.

Em determinadas situações não há necessidade de utilizar os muitos recursos das classes que tratam o padrão Observer na API do Java. Na aplicação dos taxis, por exemplo, apenas os métodos addObserver e notifyObservers foi utilizado para os observados e update para os observadores. Já outras aplicações mais robustas podem requerer métodos adicionais que encapsulam as notificações para restringi-las a determinados observadores, por exemplo.