Padrão Observer No padrão Observer temos dois objectos: um, designado Sujeito (Subject) que possui uma dada informação que pode variar ao longo da execução do programa, e outro, designado Observador (Observer) que está interessado em ser notificado sempre que essa informação varie. O padrão Observer diz-nos como estruturar as classes e objectos de modo a que o Sujeito e o Observador tenham baixo acoplamento, minimizando a interdependência entre estes dois objetos. Os objectos Observadores registam-se (subscrevem ) no objecto Sujeito para receberem actualizações quando os dados do objecto Sujeito mudarem. Quando os dados no objecto Sujeito mudam, os observadores são notificados. O padrão Observer define uma relação de dependência entre o sujeito e os observadores, de um-para-muitos, tal que quando um objecto (sujeito) muda de estado, todos os seus dependentes (observadores) são notificados e actualizados automaticamente. A relação é entre um sujeito e muitos observadores. interface IObservavel: public interface IObservavel { void registarObservador(IObservador o); void removerObservador(IObservador o); void notificarObservadores(); // // // // regista um IObservador remove um IObservador notifica todos os Observadores quando o estado do Sujeito muda } classe Sujeito: import java.util.ArrayList; public class Sujeito implements IObservavel { private ArrayList observadores = new ArrayList(); private int temperatura; //dados que podem variar; // regista um IObservador public void registarObservador(IObservador o) { observadores.add(o); } // remove um IObservador public void removerObservador(IObservador o) { int i = observadores.indexOf(o); if (i>=0) observadores.remove(i); } // notifica todos os Observadores quando o estado do Sujeito muda public void notificarObservadores() { for (int i = 0; i < observadores.size(); i++) { IObservador observador = (IObservador) observadores.get(i); observador.actualizar(temperatura); } } // simula ocorrencia de variacoes dos dados public void iniciarVariacaoTemperatura() { while (true) { try { Thread.sleep(5000); } catch (InterruptedException e) {} int variacao = 1 + (int) (5 * Math.random()); int sinalVariacao = (Math.random() < 0.5) ? -1 : 1; variacao = sinalVariacao * variacao; mudaTemperatura(variacao); } } private void mudaTemperatura(int variacao) { this.temperatura += variacao; notificarObservadores(); } } interface IObservador: public interface IObservador { void actualizar(int temperatura); } classe Observador1: public class Observador1 implements IObservador { private int temperatura; public void actualizar(int temperatura) { this.temperatura = temperatura; display(); } public void display() { System.out.println("Observador1: temperatuta actual= " + temperatura); } } classe Observador2: public class Observador2 implements IObservador { private int temperatura; public void actualizar(int temperatura) { this.temperatura = temperatura; display(); } public void display() { System.out.println("Observador2: temperatuta actual= " + temperatura); } } classe Main: public class Main { public static void main(String[] args) { Sujeito sujeito = new Sujeito(); IObservavel suj = sujeito; IObservador obs1 = new Observador1(); IObservador obs2 = new Observador2(); suj.registarObservador(obs1); suj.registarObservador(obs2); sujeito.iniciaVariacaoTemperatura(); } } A classe do objeto Sujeito não conhece os objetos Observadores concretos que notifica. Apenas sabe que implementam a interface IObservador, o que lhe permite conhecer uma interface comum a todos os observadores para lhes comunicar alterações no seu estado. O objeto sujeito apenas depende de uma lista de objetos que implementam a interface IObservador. Em qualquer altura (mesmo em runtime) podemos adicionar ou remover observadores. Não é necessário modificar a classe do objeto Sujeito para adicionar ou remover observadores. A classe do objeto Sujeito implementa a interface IObservavel, o que garante que o objecto Sujeito pode registar observadores, remover observadores e notificar observadores. Para registar ou remover um observador num objeto Sujeito usase a interface IObservador. Deste modo um objeto Sujeito, sempre que o seu estado muda, notifica um objeto observador, mas apenas existe um fraco acoplamento entre estes dois objetos. As classes dos objetos Observadores também não conhecem o objeto Sujeito. No padrão Observer os objectos sujeito e observadores estão fracamente acoplados, isto é, podem interatuar mas têm pouco conhecimento uns dos outros. Mudanças quer na classe Sujeito quer numa classe Observador não afetarão a outra, desde que estas classes implementem as respectivas interfaces. O baixo acoplamento entre estas duas classes permite-nos fazer mudanças em qualquer uma, desde que os objectos Sujeito e Observador cumpram o contrato especificado nas respetivas interfaces. Padrão Observer da API do Java A linguagem Java tem suporte para o padrão Observer incorporado no package java.util: classe Observable (semelhante ao interface IObservavel), e interface Observer (semelhante ao interface IObservador). Observable é uma classe e não uma interface, portanto a classe Sujeito deve estender a superclasse java.util.Observable. A classe Observable da API do Java já implementa todos os métodos necessários pra fornecer o comportamento observador/observável. A classe Sujeito a criar, subclasse de Observable, só necessita de aceder ao estado interno do objecto Observável, para sinalizar que o estado mudou (setChanged) e enviar notificação para todos os observadores registados (notifyObservers). public class java.util.Observable { // adiciona um observador ao conjunto dos observadores deste objeto public synchronized void addObserver(Observer o) // remove um observador ao conjunto dos observadores deste objeto public synchronized void deleteObserver(Observer o) // coloca uma flag interna que indica que o estado deste objeto Observable mudou protected synchronized void setChanged() // coloca uma flag interna que indica que o estado deste objeto Observable não mudou protected synchronized void clearChanged() // verifica a flag interna para ver se o estado deste Observable mudou e notifica todos os // observadores; o argumento arg indica o que mudou public void notifyObservers(Object arg) // verifica a flag interna e notifica todos os observadores se o objeto mudou public void notifyObservers() . . . } O método notifyObservers(Object arg) verifica se o estado deste Observable mudou, como indicado pelo método hasChanged, notifica todos os observadores e em seguida invoca o método clearChanged para colocar uma flag interna para indicar que este objecto não mudou. Cada objecto observador tem o seu método update com dois argumentos: este objeto sujeito e o argumento arg. O argumento arg pode ser usado para indicar que atributo do sujeito que mudou. O método notifyObservers() é semelhante ao de cima mas não envia o argumento arg. Neste caso o observador não recebe indicação sobre qual o atributo do sujeito que mudou. A interface Observer declara o seguinte método: update(Observable obs, Object arg) Uma classe implementa a interface Observer quando quer ser informada de mudanças em objetos Observable. O método notifyObservers da classe Observable notifica todos os objectos Observer registados invocando para cada um o método update(Observable obs, Object arg). O 1.º argumento do método update serve para o observer poder interrogar o objecto observable para determinar o seu novo estado. classe Sujeito: import java.util.Observable; public class Sujeito extends Observable { private int temperatura = 20; //dados do sujeito que podem variar // simula ocorrencia de variacoes da temperatura public void iniciaVariacaoTemperatura() { while (true) { try { Thread.sleep(5000); } catch (InterruptedException e) {} int variacao = 1 + (int) (5 * Math.random()); int sinalVariacao = (Math.random() < 0.5) ? -1 : 1; variacao = sinalVariacao * variacao; mudaTemperatura(variacao); } } private void mudaTemperatura(int variacao) { this.temperatura += variacao; setChanged(); notifyObservers(temperatura); } } classe Observador1: import java.util.Observer; import java.util.Observable; public class Observador1 implements Observer { private int temperatura; public void update(Observable sujeito, Object temperatura) { this.temperatura = (Integer)temperatura; display(); } public void display() { System.out.println("Observador1: temperatuta actual= " + temperatura); } } classe Observador2: import java.util.Observer; import java.util.Observable; public class Observador2 implements Observer { private int temperatura; public void update(Observable sujeito, Object temperatura) { this.temperatura = (Integer)temperatura; display(); } public void display() { System.out.println("Observador2: temperatuta actual= " + temperatura); } } classe Main: public class Main { public static void main(String[] args) { Sujeito sujeito = new Sujeito(); Observable suj = sujeito; Observer obs1 = new Observador1(); Observer obs2 = new Observador2(); suj.addObserver(obs1); suj.addObserver(obs2); sujeito.iniciaVariacaoTemperatura(); } }