sexta-feira, 26 de junho de 2009

Executors/FutureTask Vs. Oberver

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:

Khristian disse...

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

analytics