Anda di halaman 1dari 9

O padro de projeto Observer-Observable.

Exemplo de aplicao usando MVC com interface grfica e threads Miguel Jonathan DCC/UFRJ Julho de 2010 rev. 5 Junho 2011. O padro de projeto Observer-Observable: Esse padro, tambm conhecido como modelo-vista, realiza um mecanismo similar ao realizado pelos geradores de eventos e ouvintes no pacote Swing, mas que pode se aplicar a qualquer objeto que sofra alguma alterao no seu estado interno. A ideia que um determinado objeto poder ser observado por um nmero indeterminado de objetos observadores, que se devem se registrar previamente com ele. Cada vez que o objeto tiver seu estado alterado, ele envia a si mesmo uma mensagem, significando eu mudei, que liga um flag interno dele. Em seguida, ele envia tambm a si mesmo uma mensagem avisar meus observadores. O efeito dessa segunda mensagem enviar a todos os objetos que se registraram como seus observadores uma mensagem padro como atualize-se, e tambm ressetar o flag que indica mudana. Cada observador implementa ento da forma que lhe aprouver um mtodo para se atualizar: normalmente pede dados do objeto observado e atualiza alguma vista. O mecanismo permite que vrios observadores possam ser associados (e desassociados) a um determinado objeto observvel, sem que a classe desse objeto precise tomar conhecimento, ou ser modificada, com essa movimentao de observadores. Implementao em Java Esse padro j vem implementado em Java com a classe java.util.Observable e a interface java.util.Observer. O objeto que ser observado deve ser de uma subclasse de Observable. E os objetos observadores devem implementar a interface Observer. A API da classe Observable inclui diversos mtodos, os mais importantes so:
addObserver(Observer umObserver) adiciona o

objeto do argumento sua lista de observadores (que deve ser de uma classe que implementa Observer) o objeto do argumento da sua lista de observadores (que deve ser de uma classe que implementa Observer)

removeObserver(Observer umObserver) remove

setChanged()

- marca esse objeto como tendo mudado. O mtodo hasChanged() vai passar a retornar true.

clearChanged() - desmarca esse objeto como tendo mudado. Normalmente o mtodo notifyObservers() invoca esse mtodo aps notificar os observadores de uma

mudana.
notifyObservers()e notifyObservers(Object o)- quando esse mtodo enviado a um objeto Observable obs, se o mtodo obs.hasChanged() retorna true, ento o

efeito enviar a todos os seus observadores registrados a mensagem update(Observable obs, Object o). Alm disso, esse mtodo envia a mensagem clearChanged() ao objeto obs. O segundo argumento do mtodo update ser null, ou o argumento do mtodo notifyObservers.

A API da interface Observer bem simples: contm apenas o mtodo:


void update(Observable o, Object o);

Cada objeto observador deve implementar essa interface, ter o mtodo update. . O primeiro argumento de update() o objeto observado, e o segundo null ou um argumento opcional que poder ser includo no mtodo notifyObservers()a ser enviado pelo objeto observado. Exemplo de aplicao do padro Observer/Observable com MVC, Thread e Swing Esse exemplo reproduz a corrida entre dois times mostrada no texto Threads em Java, mas utilizando uma interface grfica e o padro de projeto Observer-Observable. A ideia criar duas instncias da classe Corrida, e criar duas threads, uma para cada instncia. uma subclasse de Observable.

Corrida

O mtodo run() da classe Corrida gera os nmeros de 1 a 100 em sequencia, e cada nmero gerado atualiza uma varivel de instncia valor. A cada alterao dessa varivel, os mtodos setChanged() e notifyObservers() sero acionados, fazendo com que as vistas, que so os observadores dessa corrida, recebam uma mensagem update. As vistas rodam o seu update particular, que solicita da corrida o valor, e o imprime na prxima linha da vista. O primeiro exemplo mostra uma vista que utiliza uma interface grfica gerada com Swing:

1. O pacote do Modelo O pacote model possui a classe Corrida. A classe implementa a interface Runnable, e o seu mtodo run() contm os comandos que sero executados na thread de cada corrida. Alm disso, essa classe uma subclasse de java.util.Observable, significando que cada instncia vai manter registros de outros objetos interessados em receberem notificao de que essa instncia mudou seu estado. Esses outros objetos devero implementar a interface java.util.Observer. No caso desse exemplo, haver apenas um observador para cada corrida, que ser a interface grfica.

Uma instncia de Corrida tem as seguintes variveis:


boolean terminou; // indica se essa corrida j chegou a 100 String time; // o nome do time. int valor; // o valor do ltimo nmero gerado

Os mtodos de instncia so:


public void iniciar()

cria a thread da corrida, e d start nela.


public void run()

Esse o mtodo que faz esse time correr em uma thread prpria. A varivel valor vai variar de 1 a 100, com intervalo de uma certa quantidade de milisegundos entre cada alterao. O intervalo ser dado por um valor aleatrio entre 0 e 399 milissegundos. A cada alterao da varivel valor os mtodos setChanged() e notifyObservables() sero acionados, fazendo com que os objetos Observer que se registraram com essa instncia recebam a mensagem update(Observable obs, Object obj). O intervalo de tempo entre cada mudana de valor para a corrida no acabar muito rpido, e poder ser apreciada. O mtodo sleep(long miliseg) bloqueia a execuo da thread por esse tempo, e pode lanar uma exceo do tipo InterruptedException caso a thread receba uma mensagem interrupt() durante o tempo em que estiver bloqueada. Nesses casos, o normal fazer com que o bloco catch execute o comando return, fazendo a thread terminar. Depois que o lao termina, e o valor chegou a 100, o mtodo faz a varivel booleana terminou ser true, e aciona os mtodos setChanged() e notifyObservables()para acionar o mtodo update dos seus observadores. Abaixo o cdigo do mtodo run():
public void run() { Random rand = new Random(); for(valor=0; valor < 100; valor++){ setChanged(); notifyObservers(); try{ Thread.sleep(rand.nextInt(400)); } catch(InterruptedException e){ System.out.println("Terminou por interrupo"); return; } } terminou = true; setChanged(); notifyObservers(); }

O cdigo completo da classe Corrida:


package model;

import java.util.Observable; import java.util.Random; public class Corrida extends Observable implements Runnable { boolean terminou; String time; int valor; public Corrida(String time){ this.time = time; valor = 0; terminou = false; } public void iniciar(){ (new Thread(this,time)).start(); } @Override public void run() { Random rand = new Random(); for(valor=0; valor < 100; valor++){ setChanged(); notifyObservers(); try{ Thread.sleep(rand.nextInt(400)); } catch(InterruptedException e){ System.out.println("Terminou por interrupo"); return; } } terminou = true; setChanged(); notifyObservers(); } public String getTime() { return time; } public void setTime(String time) { this.time = time; } public boolean isTerminated() { return terminou; } public int getValor() { return valor; } }

2. O pacote do Controle: O pacote controller contm a classe ControleCorrida. Essa classe que controla a corrida entre dois times. O seu construtor cria duas instncias de Corrida, uma para cada time. As variveis de instncia so:
Corrida corr1, corr2; String vencedor;

O mtodo mais importante iniciarCorrida(), que dispara as threads das duas corridas:
public void iniciarCorrida(){ corr1.iniciar(); corr2.iniciar(); }

Uma instncia dessa classe intermediar as solicitaes da GUI. O cdigo completo da classe ControleCorrida:
package controller; import model.Corrida; public class ControleCorrida { Corrida corr1, corr2; String vencedor; public ControleCorrida (String time1, String time2){ corr1 = new Corrida(time1); corr2 = new Corrida(time2); vencedor = null; } public String getVencedor() { return vencedor; } public void setVencedor(String vencedor) { this.vencedor = vencedor; } public Corrida getCorr1() { return corr1; } public Corrida getCorr2() { return corr2; } public void iniciarCorrida(){ corr1.iniciar(); corr2.iniciar(); } }

3. O pacote da Vista: O pacote view contm a classe GuiCorrida.e a classe MainCorrida. A primeira utiliza o pacote Swing, e a segunda a console. A classe GuiCorrida: Essa classe tem uma classe interna, ObservadorTime, que implementa a interface Observer. A classe GuiCorrida possui uma varivel de instncia dessa classe, obs1, que vai referenciar o objeto observador das duas corridas.

Na classe ObservadorTime est o mtodo update, que ser acionado cada vez que um valor for modificado em uma das corridas, ou ela terminar. O mtodo traz no seu primeiro argumento uma referncia do objeto observado, que ser a corrida que mudou. O efeito desse mtodo escrever uma frase informando esse fato, na rea de Texto da janela. No caso de uma corrida ter terminado primeiro (a sua varivel terminou tornou-se true, e o vencedor do controle ainda nulo), o mtodo define o vencedor e escreve o nome do time vencedor no campo de texto correspondente da janela:
class ObservadorTime implements Observer{ public void update(Observable obs, Object obj){ Corrida corr = (Corrida)obs; ta.append(corr.getTime()+ ": " + corr.getValor()+ "\n"); if(corr.isTerminated() && controle.getVencedor()==null){ controle.setVencedor(corr.getTime()); tfVenc.setText(corr.getTime()+ "!"); } } }

A GUI possui uma janela com um boto para iniciar o jogo. O ouvinte do boto a classe interna OuvinteBotao, que tem o mtodo actionPerformed(ActoinEvent e) que ser acionado quando o boto for clicado. Esse mtodo captura os nomes dos dois times que foram escritos nos dois campos de texto da janela, e cria o controlador. O controlador, por sua vez, cria as duas corridas. A seguir, atravs do controlador, o mtodo registra os dois observadores, um com cada corrida, e pede ao controlador para iniciar a corrida. O cdigo da classe:
class OuvinteBotao implements ActionListener{ public void actionPerformed(ActionEvent ev){ String time1 = tfT1.getText(); String time2 = tfT2.getText(); tfVenc.setText("Sem vencedor ainda"); ta.setText(""); controle = new ControleCorrida(time1, time2); controle.getCorr1().addObserver(obs1); controle.getCorr2().addObserver(obs1); controle.iniciarCorrida(); } }

O restante do cdigo da GuiCorrida o construtor, que define os diversos componentes e os dispe na janela. Abaixo o cdigo completo do pacote view, com a classe GuiCorrida:
package view; import controller.ControleCorrida; import javax.swing.*; import model.Corrida; import import import import import java.awt.BorderLayout; java.awt.Color; java.awt.Font; java.awt.GridLayout; java.awt.event.ActionEvent;

import java.awt.event.ActionListener; import java.util.Observer; import java.util.Observable; public class GuiCorrida { JFrame janela; JPanel pGeral, pTime1,pTime2, pBotao, pVenc, pOper, pDados; JScrollPane scroll; JLabel lblTime1, lblTime2, lblVenc, lblCab; JTextField tfT1, tfT2,tfVenc; JButton botIniciar; JTextArea ta; ControleCorrida controle; Observer obs1, obs2; public GuiCorrida(){ obs1 = new ObservadorTime(); obs2 = new ObservadorTime(); janela = new JFrame("Corrida dos times"); pGeral = new JPanel(new GridLayout(1,3)); pOper = new JPanel(new GridLayout(4,1)); pTime1 = new JPanel(); lblTime1 = new JLabel("Time 1: "); tfT1 = new JTextField(20); pTime1.add(lblTime1); pTime1.add(tfT1); pOper.add(pTime1); pTime2 = new JPanel(); lblTime2 = new JLabel("Time 2: "); tfT2 = new JTextField(20); pTime2.add(lblTime2); pTime2.add(tfT2); pOper.add(pTime2); pBotao = new JPanel(); botIniciar = new JButton("Iniciar"); botIniciar.addActionListener(new OuvinteBotao()); pBotao.add(botIniciar); pOper.add(pBotao); pVenc = new JPanel(); tfVenc = new JTextField(20); tfVenc.setFont(new Font("Arial", Font.BOLD, 20)); tfVenc.setForeground(Color.red); lblVenc = new JLabel("Vencedor"); lblVenc.setFont(new Font("Arial", Font.BOLD, 14)); pVenc.add(tfVenc); pVenc.add(lblVenc); pOper.add(pVenc); pGeral.add(pOper); pDados = new JPanel(new BorderLayout()); lblCab = new JLabel("JOGADAS"); ta = new JTextArea(); ta.setFont(new Font("Arial", Font.PLAIN, 14)); scroll = new JScrollPane(ta); pDados.add(lblCab, BorderLayout.NORTH); pDados.add(scroll, BorderLayout.CENTER);

pGeral.add(pDados); janela.add(pGeral); janela.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); janela.setBounds(0, 0, 800, 550); janela.setVisible(true); } class OuvinteBotao implements ActionListener{ public void actionPerformed(ActionEvent ev){ String time1 = tfT1.getText(); String time2 = tfT2.getText(); tfVenc.setText("Sem vencedor ainda"); ta.setText(""); controle = new ControleCorrida(time1, time2); controle.getCorr1().addObserver(obs1); controle.getCorr2().addObserver(obs2); controle.iniciarCorrida(); } } class ObservadorTime implements Observer{ public void update(Observable obs, Object obj){ Corrida corr = (Corrida)obs; ta.append(corr.getTime()+ ": " + corr.getValor()+ "\n"); ta.selectAll(); if(corr.isTerminated() && controle.getVencedor()==null){ controle.setVencedor(corr.getTime()); tfVenc.setText(corr.getTime()+ "!"); } } } public static void main(String[] args) { new GuiCorrida(); } }

A classe MainCorrida: Essa classe mostra a mesma corrida com uma vista na console. O modelo e o controle so os mesmos. Cada corrida tem um time, e um objeto observador da classe MainCorrida. Abaixo, o cdigo da classe:
package view; import model.Corrida; public class MainCorrida implements java.util.Observer{ Corrida corrida; String time; int valor; public MainCorrida(String time){ corrida = new Corrida(time); corrida.addObserver(this); } public void update(java.util.Observable obs, Object obj){ valor = corrida.getValor();

System.out.println(corrida.getTime()+ ": " + valor); if (valor == 100){ System.out.println("Terminou " + corrida.getTime() + "!!"); } } public void iniciarCorrida(){ corrida.iniciar(); } public static void main(String[] args) { new MainCorrida("Flamengo").iniciarCorrida(); new MainCorrida("Botafogo").iniciarCorrida(); } }