o-bidi-font-weight: normal">Parte 2: JTable, MVC Aplicado e Tratamento de Eventos

 

Saiba como customizar componentes JTable, organizar o tratamento de eventos, estruturar uma aplicação visual para facilitar extensões e manutenções.

 

Nesta edição, damos continuidade à construção da aplicação iniciada na edição anterior, apresentando conceitos fundamentais de desenvolvimento Java e recursos da série 4.x do IDE livre NetBeans. A primeira parte foi focada na programação visual e em como o NetBeans pode ser utilizado para prototipar uma interface com usuário baseada  no Swing, além de mostrar características dos principais gerenciadores de layout.

Nesta segunda parte, passamos ao editor de código. Vamos customizar o visual da tabela que exibe as tarefas, para indicar com cores diferentes tarefas completadas, atrasadas ou em estado de alerta. Também iremos tratar dos eventos gerados pelos componentes da interface, evoluir a arquitetura e saber pelos componentes da interface, evoluir a arquitetura e saber como evitar que o tratamento de eventos transforme seu código orientado e objetos em “código espaguete”.

 

Arquitetura MVC em aplicações Gráficas

Durante a prototipação da interface gráfica, buscamos ficar o máximo possível dentro do editor visual do NetBeans. O objetivo era apenas criar uma “casca” visual para a aplicação que pudesse ser avaliada e discutida com os usuários. Agora vamos começar a colocar lógica por trás dessa casca, permitindo avaliar e testar a real funcionalidade da aplicação.

É comum, no desenvolvimento de aplicações visuais, acabar gerando código desorganizado, onde uma modificação em qualquer parte gera efeitos colaterais nos locais mais inesperados; ou onde a simples adição de uma informação extra exige uma cascata de mudanças em várias partes da aplicação.

Para evitar isso, vamos adotar uma arquitetura muito popular em aplicações interativas, a arquitetura MVC (Movel-View-Controller, Modelo-Visão-Controlador). A MVC foi usada ou descrita em vários artigos na Java Magazine (em maior parte no contexto de aplicações web). Nela, cada classe tem um papel bem definido: tratar da exibição das informações (Visão); responder a ações do usuário (Controlador); ou cuidar da consistência e persistência dos dados (Modelo). Classes com papéis diferentes são praticamente independentes entre si, e assim é possível modificá-las sem medo de gerar “efeitos colaterais”. O uso da arquitetura MVC também torna fácil identificar onde fazer cada mudança.

Para tornar o uso da arquitetura bem explícito, iremos organizar as classes Java em três pacotes: todo.visao, todo.controle e todo.modelo.

No pacote visao estão classes gráficas (como janelas ou componentes personalizados), ou classes necessárias para o seu funcionamento. Essas classes não tomam decisões a respeito de como uma operação deve ser realizada – este é o papel das classes do pacote controle, as quais efetivamente respondem aos eventos do usuário (como cliques num item de menu) e decidem qual operação realizar. Já as classes do pacote modelo representam os dados da aplicação, e contêm a inteligência necessária para realizar ações sobre esses dados.

 

  Figura 1. Dependências entre componentes no modelo MVC

 

A Figura 1 ilustra o relacionamento entre os pacotes, usando um diagrama UML (o desenho de pasta representa um pacote e agrupa várias classes)1. Observe que o Controlador fica no “meio do caminho” entre a Visão e o Modelo2. Essa separação traz um benefício adicional: ela permite que uma mesma Visão seja reutilizada com vários Modelos contendo as mesmas informações – mas obtendo essas informações de fontes diferentes (ex.: um Modelo baseado em banco de dados e outro acessando arquivos XML). Pelo seu lado, o Modelo fica independente da interface com o usuário (que faz parte da Visão): as classes do Modelo podem, por exemplo, ser utilizadas depois, numa aplicação web ou num MIDlet J2ME.

 

Inteligência em objetos

No início da Orientação a Objetos, era comum defender-se a idéia de que um objeto deveria conter toda a “inteligência”relacionada a ele. Mas com o passar do tempo verificou-se que essa estratégia poderia levar a objetos “gordos”, concentrando mutas funcionalidades,e com manutenção difícil  e baixo desempenho. Hoje, no entanto, se considera uma alternativa válida criar também objetos “burros”, que apenas trafegam informações de um “objeto inteligente” para outro, deixando-os mais independentes entre si.

No nosso caso, os “objetos burros” serão Value Objects (VOs)( que são JavaBeans, com atributos  e seus métodos get/set, e possivelmente alguma funcionalidade localizada). E os objetos inteligentes são os objetos de Visão, Modelo e Controle.           

A única classe VO de que necessitamos agrupa todas as informações de uma tarefa, então este será o seu nome. Na nossa aplicação, teremos classes de Visão que sabem representar graficamente uma tarefa, e classes de Modelo que sabem como recuperar e salvar tarefas do bando de dados (ou de um collection etc.).

Para simplificar, a classe Tarefa ( e outros VOs que surgirem) podem ser deixados no mesmo pacote das classes de Modelo. Afinal, são as operações implementadas no Modelo que determinam quais informações deverão estar nos VOs.

 

Arquitetura da aplicação

Nossas classes de Visão – ListaTarefas (um JFrame) e EditaTarefa (um JDialog) – foram prototipadas no artigo anterior. Agora estamos preocupados em definir a interface externa destas classes, isto é, o que irão expor para o Controlador. Precisamos definir os eventos, que representam ações realizadas pelo usuário, e os métodos para manipular informações encapsuladas em VOs.

Teremos apenas uma classe no Controlador, chamada ConsultaEditaTarefas.

 Em aplicações mais complexas, poderá haver várias classes controladoras. Uma estratégia para começar é criar um controlador para cada conjunto de operações consultar-editar-apagar da aplicação.

 

Nosso exemplo também terá apenas uma classe no Modelo, chamada  GerenciadorTarefas. Esta classe é um DAO (Data Access Object) e será responsável por ler e atualizar registros no banco de dados.

Aplicações mais complexas terão classes de Modelo que encapsulam processos de negócios em vez de apenas entidades de informação.

E irão para o Controlador apenas estas classes de mais alto nível, reservando os DAOs para uso interno.

Na Figura 2 temos um modelo UML contendo as classes que iremos desenvolver. Note como todas as classes no diagrama têm dependências em relação ao VO Tarefa. (Mas como este não tem inteligência significativa, ele é com freqüência omitido em diagramas de classes, e na avaliação de dependências entre classes da aplicação).

 

Começando a construção

 Nossa idéia é prosseguir construindo a aplicação “de cima para baixo”. Iniciamos pela interface com o usuário (Visão) e vamos descendo até chegar à logica de banco de dados (Modelo). Em cada etapa será feito o mínimo de trabalho necessário para que seja possível testar uma nova funcionalidade. Assim, nossos objetos de modelo serão por algum tempo versões temporárias, mantendo os dados em memória sem acessar o bando de dados.

A classe Main (não representada na Figura 2) permanece como sendo a classe principal da aplicação, e do projeto no NetBeans. Seu papel é instanciar e conectar as classes de Visão, Modelo e Controle.

A Listagem 1 apresenta a classe Tarefa e a Listagem 2, a classe Main. Note que Tarefa fornece alguns métodos utilitários relacionados com a manipulação das datas de conclusão e prazo de alerta. Parece ir contra a recomendação de que um VO não deve ter inteligência, mas estes métodos não dependem de nada externo ao próprio objeto, então este é o seu lugar.

A Listagem 3 apresenta a classe GerenciadorTarefas, que implementa um único método: listaTarefas(). Neste ponto, o método constrói um java.util.List contendo objetos Tarefas pré-fabricados para que seja possível testar o código da Visão e do Controlador.

Você pode gerar os métodos get/set automaticamente no NetBeans. Depois de definir os atributos da classe Tarefa, clique com o botão direito no editor de código (ou na visão de projeto do NetBeans) e escolha Refactor|Encapsulate Fields.

A Listagem 4 mostra a primeira versão da classe controladora ConsultaEditaTarefas. Tudo o que ela faz no momento é passar para a Visão (ListaTarefas) a lista de objetos Tarefa retornada pelo Modelo (GerenciadorTarefas). Note que as classes de Visão devem ter métodos para receber as tarefas, e é responsabilidade da Visão repassar os dados das tarefas para componentes visuais internos, como caixas de texto e componente JTable.

 

 Figura 2. Principais classes da aplicação Lista de Tarefas

 

Modelo da aplicação e models do Swing

Na maioria dos toolkits gráficos, os objetos visuais de tabela e de lista manipulam apenas arrays de strings; para preenchê-los, os dados devem ser convertidos em strings e inseridos linha a linha (ou célula a célula). Esta abordagem tem dois problemas: aumento do consumo de memória, pois os dados são duplicados na tabela; e maior tempo gasto para inserir um conjunto extenso de dados.

No Swing, entretanto, os dados para exibição num componente de tabela (JTable) devem se passados num classe que implementa a interface TableModel. Esta classe fica responsável por fornecer para a tabela os dados das células visíveis, e em informar quando os dados forem modificados. Com isso, não são acrescentadas ou alteradas linhas na tabela em si; essas operações são realizadas no TableModel associado à tabela.

O Swing fornece a classe DefaultTableModel, que armazena dados em vetores de vetores (java.util.Vector), emulando a abordagem dos outros toolkits. Isso permite começar rapidamente, mas o recomendado mesmo é criar o seu próprio TableModel, que acessa diretamente os VOs. Dessa forma não há duplicação de dados e se obtêm ganhos de performance, pois a tabela pede ao seu modelo apenas os dados que irá efetivamente utilizar. Todos os componentes do Swing seguem a abordagem descrita: em vez de o próprio componente armazenar as informações que exibe, essa responsabilidade e delegada para um objeto model do Swing. (O uso do nome “model” nas classes e interfaces utilizadas pelos componentes do Swing não é coincidência. Para uma discussão mais aprofundada sobre o assunto, consulte o quadro “Arquitetura MVC e o Swing”.)

 

Listagem 1. VO Tarefa                                                                                          

 

package todo.modelo;

 

import java.util.Date;

import Java.util.Calendar;

 

    public class Tarefa {

        private int id;

private String descricao;

private int prioridade;

private Date dataConclusao;

private boolean gerarAlerta;

private int diasAlerta;

private String observacoes;

private boolean concluida;

 

private Calendar getDataHojeNormalizada()

            Calendar hoje - Calendar.getlnstanceC);

hoje.set(Calendar.HOUR_OF_DAY. 0);

hoje.set(Calendar.MINUTE. 0);

hoje.set(Calendar.SECOND. 0);

hoje.set(Calendar.MILLISECOND, 0);

return hoje;

       }

 

public boolean isAtrasada() {

            Date conclusao = getDataConclusao();

if (conclusao == null)

return false;

           else {

return conclusao.compareTo(

getDataHojeNormalizada().getTime()) < 0;

          }

     }

 

     public boolean isAlertaAtivo() {

        Date conclusao = getDataConclusao();

        if (!isGerarAletra() || conclusao == null)

            return false;

       else {

           Calendar diaConclusao = Calendar.getlnstance();

           diaConclusao.setTime(getDataConclusao());

          int dias = getDataHojeNormalizada().get(Calendar.DAY_OF_YEAR)

          - diaConclusao.get(Calendar.DAY_OF_YEAR);

        return dias <= getDiasAlerta();

    }

  }

 

   public Tarefa() {

   setConcluida(false);

   setGerarAlerta(false);

   }

 

   // ... métodos get/set

}

 

Listagem 2. Classe principal da aplicação                                                                 

 

package todo;

 

import javax.swing.*;

import todo.visao.ListaTarefas;

...

Quer ler esse conteúdo completo? Tenha acesso completo