Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
O Observer é um design pattern do tipo comportamental, onde a alteração do estado de um objeto influencia em outros, basicamente é uma relação de um para muitos, onde qualquer alteração no objeto observado pode fazer com que os seus observadores alterem o seu estado também.
Nesse padrão basicamente temos dois papéis, o observado e os observadores, onde o observado geralmente possui um estado, que ao ser alterado é notificado aos observadores que esse estado foi alterado, com isso cada observador pode reagir a mudança.
Esse padrão promove um desacoplamento entre os papéis, esse padrão também é altamente escalável permitindo adicionar muitos observadores mesmo ao longo de implementações futuras, porque mantém uma baixa dependência entre os objetos, e com isso temos um sistema mais flexível e extensível.
Vamos implementar esse padrão, para isso vamos definir uma interface que irá definir os métodos necessários para subscrição dos observadores, remoção e notificação toda vez que o estado do nosso observado for alterado.
public interface Observado {
void adicionarObservador(Observador observador);
void removerObservador(Observador observador);
void notificarObservadores();
}
Observado.javaAgora precisamos de uma interface comum para os nossos observadores, isso é importante porque será o contrato que definirá qual é o método chamado quando eles forem notificados.
public interface Observador {
void atualização(Observado observado);
}
Observador.javaPronto agora todas as nossas interfaces estão criadas, vamos de fato implementar um objeto que será observado, e nesse ponto é bem simples, basicamente precisamos implementar a interface Observado e adicionar uma variável para eu guardar meus observadores.
public class ObservadoConcreto implements Observado {
// Variável responsável por guardar os observadores
private List<Observador> observadores = new ArrayList<Observador>();
// Estado do meu observado
private int estado;
@Override
public void adicionarObservador(Observador observador) {
observadores.add(observador);
}
@Override
public void removerObservador(Observador observador) {
observadores.remove(observador);
}
@Override
public void notificarObservadores() {
for(Observador observador : observadores) {
observador.atualizacao(this);
}
}
// Métodos responsáveis pela alteração do estado
public void setEstado(int estado) {
this.estado = estado;
notificarObservadores();
}
public int getEstado() {
return this.estado;
}
}
ObservadoConcreto.javaNote que a implementação é bem simples, adicionamos uma lista para guardar nosso observadores, nos métodos de adição e remoção estamos utilizando os métodos disponibilizados no próprio List.
No método principal que é o nosso “notificarObservadores”, também é bem simples precisamos apenas percorrer nossa lista que contém nossos observadores e utilizar o método atualizar definido na interface, passando o nosso próprio observado, para que na outra ponto o observador tenha acesso aos dados exposto pelo nosso observado.
Agora precisamos implementar nossos observadores, que acaba sendo uma tarefa bem simples, vamos apenas implementar a interface e sobrescrever o método, segue o exemplo:
Observador 1
public class Observador1 implements Observador {
@Override
public void atualizacao(Observado observado) {
ObservadoConcreto observadoConcreto = (ObservadoConcreto) observado;
System.out.println(String.format("Observador 1: %d", observadoConcreto.getEstado()));
}
}
Observador1.javaObservador 2
public class Observador2 implements Observador {
@Override
public void atualizacao(Observado observado) {
ObservadoConcreto observadoConcreto = (ObservadoConcreto) observado;
System.out.println(String.format("Observador 2: %d", observadoConcreto.getEstado()));
}
}
Observador2.javaAgora de fato vamos instanciar nossos objetos e validar o funcionamento, para isso criamos uma classe com o método main, e vamos capturar os valores do terminal.
public class ExemploObserver {
public static void main(String[] args) {
// Instanciando o observado
ObservadoConcreto observado = new ObservadoConcreto();
// Adicionando os observadores
observado.adicionarObservador(new Observador1());
observado.adicionarObservador(new Observador2());
// Criar um scanner para caputurar valores do terminal
Scanner scanner = new Scanner(System.in);
int numero = 0;
while(numero != -1) {
System.out.print("Digite um número inteiro: ");
numero = scanner.nextInt();
// Vamos fazer a mudança de estado do nosso observado
observado.setEstado(numero);
}
// fechar o scanner
scanner.close();
}
}
ExemploObserver.javaTestando a nossa implementação perceba que toda vez que digitamos um valor no terminal o estado do nosso observado é alterado e por sua vez ele notifica os nossos observadores que por sua vez reagem a essa alteração.
O Java já nos disponibiliza interfaces que facilitam essas implementações, mas elas foram marcadas como Deprecated a partir da versão 9, isso significa que embora elas estejam ainda presentes, podem ser removidas em versões futuras, e a orientação é que nós mesmos implementamos nossas próprias soluções de observable, para título de curiosidade, vamos demonstrar como seria utilizando as interfaces disponibilizadas pelo Java.
Observado
public class Observado extends Observable {
private int estado;
public int getEstado() {
return estado;
}
public void setEstado(int estado) {
this.estado = estado;
setChanged();
notifyObservers(this);
}
}
Observado.javaObservadores
public class Observador1 implements Observer {
@Override
public void update(Observable observable, Object arg1) {
Observado observado = (Observado) observable;
System.out.println("Observador 1 " + observado.getEstado());
}
}
Observador1.javapublic class Observador2 implements Observer {
@Override
public void update(Observable observable, Object arg1) {
Observado observado = (Observado) observable;
System.out.println("Observador 2 " + observado.getEstado());
}
}
Observador2.javaE o funcionamento fica basicamente o mesmo toda vez que eu realizar uma alteração de estado do nosso observado, nossos observadores serão notificados através do método update.
Embora esse seja um excelente padrão para desacoplar funcionalidades dentro da sua aplicação é importante observar que alguns problemas podem ser ocasionados quando se adota esse pattern, vamos listar alguns exemplos:
Esse padrão de fato é bem poderoso, podemos facilmente ir adicionando observadores que reagiram a mudanças de estado de forma desacoplada, lembrando que desacoplar as soluções é sempre uma boa prática, principalmente para implementações futuras e manutenção. Você ainda pode optar por transformar o nosso Observador em uma interface funcional, isso ajuda a reduzir bastante o código além de poder utilizar “generics” para caso o estado do seu observado seja complexo.