Você está na página 1de 7

Exemplo de aplicao usando MVC com interface grfica, threads e o padro Observer-Observable.

Miguel Jonathan DCC/UFRJ Julho de 2010 rev. Junho 2011. O padro de projeto Observer-Observable: Esse padro 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 como eu mudei que liga um flag interno dele. Alm disso ele envia tambm a si mesmo a mensagem avisar meus observadores. Essa mensagem faz com que todos os objetos que se registraram como observadores desse objeto recebam uma mensagem padro como atualize-se. 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 uma quantidade indeterminada de 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. Na classe Observable esto as mensagens setChanged() (equivale a eu mudei ) e notifyObservers() (que equivale a avisar meus observadores). O objeto observado envia ento automaticamente a cada um dos seus observadores a mensagem update() (equivale a atualizese). Por outro lado, cada observador obrigado a implementar uma verso prpria do mtodo update(), por conta da exigncia da interface Observer. Exemplo de aplicao Neste exemplo, criamos uma janela no Swing para simular uma corrida entre dois times. Cada time corre em uma thread prpria, e tem que gerar os nmeros de 1 a 100. Vence o time que chegar a 100 primeiro. Os nomes dos dois times so escritos nos dois campos de texto, e o boto "Iniciar" acionado, para a corrida comear, como mostra a figura:

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));

} terminou = true; setChanged(); notifyObservers();

} catch(InterruptedException e){ System.out.println("Terminou por interrupo"); return; }

public String getTime() { return time; } public void setTime(String time) { this.time = time; } public boolean isTerminou() { 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. Essa classe tem uma classe interna, ObservadorTime, que implementa a interface Observer. A GuiCorrida possui duas variveis de instncia dessa classe, obs1 e obs2, e cada uma ser observadora de uma das 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 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.isTerminou() && 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(obs2); 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 model.Corrida; import javax.swing.*; import java.awt.BorderLayout; import java.awt.GridLayout; import 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); lblVenc = new JLabel("Vencedor"); pVenc.add(tfVenc); pVenc.add(lblVenc); pOper.add(pVenc); pGeral.add(pOper); pDados = new JPanel(new BorderLayout()); lblCab = new JLabel("JOGADAS"); ta = new JTextArea(); 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, 600); 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"); if(corr.isTerminou() && controle.getVencedor()==null){ controle.setVencedor(corr.getTime()); tfVenc.setText(corr.getTime()+ "!"); } } } public static void main(String[] args) { new GuiCorrida(); } }