Usando o pattern Observable

em 09/03/2004 , por Samuel Mota
Introdução
Este tutorial irá explicar como criar uma GUI (Graphical User Interface) que não congele quando a aplicação estiver em um processo longo e se mantenha respondendo aos eventos do usuário.
Esta não é uma das tarefas mais simples de se fazer pois envolve diretamente o uso de Threads e o desenho do sistema de eventos da plataforma Java, mas é muito facilitada pela existência de mecanismos para implementar o Pattern Observer. Como o objetivo é ser apenas um tutorial caso queira se aprofundar no assunto veja este site ou procure mais sobre o pattern. Alguns conceitos importantes:
  • Thread: é uma linha de execução do programa. A aplicação pode ter uma ou N threads, cada uma dessas linhas é executada independentemente da outra, (quase) em paralelo. O importante é que dentro de uma thread as instruções são processadas sequencialmente, com isso existe uma fila de instruções. É um recurso para permitir a uma mesma aplicação usar de forma mais produtiva o processamento da máquina fazendo várias tarefas ao mesmo tempo.
Exemplo
Para este exemplo utilizaremos uma tela com uma barra de progresso para manter nosso feliz usuário informado sobre o processo. O processo será simulado, mas poderá ser qualquer atividade que seu programa precise executar em resposta a um evento, não necessariamente um click de botão, neste caso o seguinte código será usado para representar o processo:

Neste exemplo a cada 10 iterações do loop vamos atualizar a barra de progresso e ao final vamos atualizar a mensagem para o usuário.
O problema
Quando executamos um processo em resposta a um evento a tela congela, ou seja, não responde a nenhum evento enquanto não for terminado o evento anterior e isso inclui redesenhar a tela para informar o usuário do progresso por exemplo. A tela ficaria assim ao alternar de aplicação no Windows: O código que gerou este problema é o seguinte:

O código acima é bastante simples e apenas executa o processo no tratamento do evento, vamos deixar para estudar o código correto mais adiante. A origem desse comportamento está no modelo adotado pelo Java para gerenciar os eventos, ou seja, foi a opção escolhida ao desenhar a arquitetura da plataforma. Existe uma única thread conhecida como event-dispatching thread onde todos os eventos são colocados e conforme vimos no início deste tutorial todas as instruções de uma thread são executadas em sequência e por isso enquanto nosso processo não terminar nenhum outro evento será processado.
A solução
A solução lógica é separarmos nosso processo da tela e criarmos uma thread só para nosso processo. Para criar threads podemos utilizar a herança da classe Thread ou implementar a interface Runnable, a explicação sobre threads em java está além do objetivo deste tutorial mas você pode entender melhor o assunto lendo o The Java Tutorial. Para fazermos essa separação utilizamos também uma interface e uma classe, Observable e Observer respectivamente. A interface Observer deve ser implementada por todos os objetos que queiram ser notificados de mudanças em outros objetos. Ela define apenas um método a ser implementado: update(Observable o, Object arg) , que será chamado a cada notificação, e é nele que faremos o controle para atualizarmos a tela. A classe Observable deve ser implementada por todos os objetos que queiram notificar mudanças a outros objetos e ela expõe vários métodos para permitir a notificação dos observadores. No nosso caso, a tela deve ser notificada e por isso implementa a interface e o processo deverá notificar alterações e por isso herda a classe. É importante notar que a maioria dos componentes do Swing não são thread safe, ou seja, não fazem o controle de acesso por thread, por isso caso seu programa utilize entradas do usuário na tela certifique-se de carrega-las no seu objeto do processo antes do processamento pois caso haja mudanças nos dados durante o processamento e o processo tentar utiliza-las poderá haver inconsistencia nos parâmetros.
Observable
Nosso processo será observado, por isso a cada mudança significativa vamos notificar os observadores. Para notificar usamos um dos métodos notify disponíveis, neste caso usamos o notifyObserver(Object) e nele passamos um objeto que terá algum significado para o objeto notificado.
Quando quisermos enviar as notificações setamos nosso objeto como alterado chamando o método setChanged(). Veja o código abaixo:

Simples assim? Sim... a complexidade não está no código mas sim no significado das mensagens para o objeto notificado.
Observer
Como vimos observer será nossa tela, o código abaixo faz o serviço:

Vamos estudar as partes importantes: Para executar nosso processo registramos o listener e chamamos o método executaProcesso, o código

primeiro verifica se o processo existe, caso exista é porque o botão foi clicado e nosso processo está sendo executado, imprimimos uma mensagem no console. Caso não exista criamos uma thread passando para o construtor a própria tela para que o processo possa nos registrar como observadores. O método update é invocado a cada vez que o objeto observado é marcado como alterado. O código

Caso receba um objeto do tipo Integer como arg apenas atualiza a barra de progresso e o texto na caixa de texto, caso receba o Boolean e este seja true significa que o processo acabou e avisamos o usuário. É neste método que está o segredo, deve-se fazer um planejamento no modo como serão notificadas as diferentes alterações para não impactar na performance (adianto que esse ai não é o ideal) e ainda assim ser significativo para a interface, aqui podemos aplicar outros patterns ou definirmos algo que melhor se encaixe no funcionamento do processo e da interface. Uma possibilidade é disponibilizarmos no objeto do processo alguns métodos para melhorar a notificação, após a tela ser notificada ela pergunta ao objeto sobre seu status. (só uma idéia:)
Resultado
O resultado deste exemplo é uma tela que mantém o usuário informado sobre o andamento do processo, mas esta abordagem permite tirarmos melhor proveito da interface com o usuário, é claro que exige também um cuidado especial de controle das açoes do usuário enquanto nosso processo roda (no exemplo existe um controle simples, lembra?) !
Conclusão
Esta não é a única solução para o problema, nem mesmo a melhor pois há limitações por se utilizar a herança para o objeto Observable (sim existem técnicas para transpor essa limitação) mas talvez seja a mais simples e mais rápida de se implementar sendo com isso muito útil para interfaces simples baseadas em caixas de diálogos ou outras aplicações. É possível apartir deste conceito construir modelos mais complexos e completos, caso tenha alguma dúvida pesquise sobre o assunto e poste as dúvidas aqui no GUJ. Até breve!