Antes de começar, este post é específico pra Java. Se não se interessar por java, pule pro próximo post, ou não.
Pra quem não conhece, o padrão observer serve basicamente pra monitorar mudanças de estado em um objeto. Como todo padrão de projeto, o observer pode ser subvertido para fazer coisas onde existem opções melhores.
Exemplo: é preciso mostrar um diálogo com "OK" e "Cancel" para o usuário, e uma ação externa deve ser executada, dependendo da resposta do usuário.
Uma primeira idéia é cadastrar um listener no diálogo, e dependendo do botão clicado, um método do listener é notificado. Um exemplo de listener seria:
/**
* Interface de um Listener.
*/
public interface Listener {
/**
* Método notificado ao cancelar o diálogo.
*/
void onFailure();
/**
* Método notificado ao aceitar o diálogo.
*/
void onSuccess();
}
Uma possivel implementação de diálog usando esse listener:
/**
* Implementação de um diálogo onde a resposta da ação do usuário é capturada
* através de um listener.
*/
class ListenerFrame {
/** Frame principal. */
private final JFrame frame;
/** Ação do botão de ok. */
private final ActionListener okAction = new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
listener.onSuccess();
}
};
/** Ação do botão de cancelar. */
private final ActionListener cancelAction = new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
listener.onFailure();
}
};
/** Instância do listener que deve ser noticada. */
private final Listener listener;
/**
* Constroi uma instância de ListenerFrame.
*
* @param l
* o listener a ser notificado.
*/
public ListenerFrame(final Listener l) {
listener = l;
frame = new JFrame();
final JButton ok = new JButton("OK");
ok.addActionListener(okAction);
final JButton cancel = new JButton("Cancel");
cancel.addActionListener(cancelAction);
final JPanel panel = new JPanel();
panel.add(ok);
panel.add(cancel);
frame.add(panel);
frame.pack();
}
/**
* Exibe o frame.
*/
public void show() {
frame.setVisible(true);
}
}
Como podem ver, no botão ok "onSuccess()" é chamado, e em cancelar "onFailure()" é chamado.
Uma classe usando esse diálogo, a título de exemplo:
/**
* Exemplo de utilização de um frame onde a resposta é dada por um listener.
*/
public class ListenerFrameExample {
/**
* O programa cria um novo ListenerFrame passando um listener como
* argumento.
*
* @param args
*/
public static void main(final String[] args) {
new ListenerFrame(new Listener() {
@Override
public void onSuccess() {
System.out.println("YAY!");
System.exit(0);
}
@Override
public void onFailure() {
System.out.println("OH NOES!");
System.exit(0);
}
}).show();
}
}
O problema aqui é que eu estou usando o padrão observer pra esperar uma resposta, e não uma mudança de estado. Se eu puder continuar com a execução do programa, isso até não tem tanto problema, mas a intenção aqui claramente não é essa. E como intensão em um código é (quase) tudo, deve haver um jeito melhor de fazer isso ;)
É ai que entra a FutureTask. Como podem na documentação, a FutureTask serve pra guardar um resultado que vai ser requisitado no futuro, e é exatamente isso que queremos: saber o futuro resultado da ação do usuário.
Primeiro, podemos esquecer a classe Listener. Toda a parde de resposta vai ser encapsulada no diálogo.
/**
* Implementação de um diálogo onde a resposta da ação do usuário é capturada
* por uma FutureTask.
*
*/
class ExecutorFrame {
/** Frame principal. */
private final JFrame frame;
/** Variável auxiliar utilizada para capturar a resposta do usuário. */
private Boolean success;
/** FutureTask criada para encapsular a resposta do usuário. */
private final FutureTask hadSuccess = new FutureTask(
new Callable() {
public Boolean call() throws Exception {
return success;
};
});
/** Ação do botão de ok. */
ActionListener okAction = new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
success = true;
// FutureTask é executada, liberando hadSuccess()
Executors.newSingleThreadExecutor().execute(hadSuccess);
}
};
/** Ação do botão de cancelar. */
ActionListener cancelAction = new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
success = false;
// FutureTask é executada, liberando hadSuccess()
Executors.newSingleThreadExecutor().execute(hadSuccess);
}
};
/**
* Constroi uma instância de ExecutorFrame.
*/
public ExecutorFrame() {
frame = new JFrame();
final JButton ok = new JButton("OK");
ok.addActionListener(okAction);
final JButton cancel = new JButton("Cancel");
cancel.addActionListener(cancelAction);
final JPanel panel = new JPanel();
panel.add(ok);
panel.add(cancel);
frame.add(panel);
frame.pack();
}
/**
* Exibe o frame.
*/
public void show() {
frame.setVisible(true);
}
/**
* Retorna a resposta do usuário. O método fica travado até que o usuário
* clique em um botão.
*
* @return {@code true} caso o usuario clique em OK, {@code false} caso
* clique em cancelar.
*/
public boolean hadSuccess() {
try {
return hadSuccess.get();
} catch (final InterruptedException e) {
} catch (final ExecutionException e) {
}
return false;
}
}
O método "get()" da FutureTask trava a execução até a tarefa seja executada. Uma FutureTask é criada para guardar o resultado, e as ações dos botões simplesmente executam a tarefa, fazendo assim com que "hadSuccess()" retorne o resultado correto.
A utlização desse dialogo fica assim:
/**
* Exemplo de utilização de um frame onde a resposta é dada por uma FutureTask.
*/
public class ExecutorFrameExample {
/**
* O programa instancia um frame e aguarda a sua resposta. Note que
* d.hadSuccess() fica travado até o usuário clicar em um botão.
*
* @param args
*/
public static void main(final String[] args) {
final ExecutorFrame d = new ExecutorFrame();
d.show();
if (d.hadSuccess()) {
System.out.println("YAY!");
} else {
System.out.println("OH NOES!");
}
System.exit(0);
}
}
Eu particularmente acho bem mais interessante desse jeito. Obviamente cada implementação tem suas vantagens e desvantagens, mas a intenção da segunda opção está bem mais clara do que a primeira.
Outras leituras:
http://en.wikipedia.org/wiki/Swing_%28Java%29
http://en.wikipedia.org/wiki/Observer_pattern
http://sourcemaking.com/design_patterns


1 comentários:
Que beleza, a melhor coisa que eu não sabia que precisava de hoje!
huaehaeuhea
Tava mesmo me enrolando com uns observers, chegou bem na hora :P
Postar um comentário