12
62 www.mundoj.com.br 62 62 62 62 www ww www ww ww www www www w www www www w.m .mu .mu u .mu .m mu .mu .mu mu mu u .mundo ndo ndo o ndo n ndo ndo n n n ndo ndo ndo ndo ndo o d do j.c j.c j.c j.c j c c j.c j.c .c .c j.c j.c j.c com om om. om. o om. m m. om. om. om. m. om om m m. m m om. m. br br br br br br br b br br br br br br br tualmente, a linguagem Java possui oito certificações, sendo que apenas duas delas são baseadas em desempenho prático, ou seja, o candidato é submetido a uma tarefa e só consegue êxito se desenvolvê-la e atingir um número mínimo de pontos após avaliação. Uma destas certificações é a SCJD (Sun Certified Java De- veloper), na qual o candidato precisa desenvolver uma atribuição de pro- gramação e um exame de redação referente ao programa desenvolvido. Na atribuição, o candidato deve construir um programa cliente/servidor que precisa seguir algumas regras e também deve preparar documentos referentes à própria atribuição. No exame de redação, o candidato deve responder a quatro perguntas genéricas (ou que se aplicam a qualquer um dos modelos de atribuição). Embora as perguntas sejam referentes a pontos específicos do projeto, o real objetivo deste exame é verificar se o projeto foi realmente desenvolvido pelo candidato que solicitou a certifi- cação, e não por outra pessoa. Vale lembrar que tudo, desde comentários JavaDoc à prova de redação, deve ser escrito em inglês. artigo De todas as certificações Java disponíveis atualmente, a SCJD é a única que efetivamente exige que o candidato mostre na prática que sabe utilizar a linguagem Java para resolver problemas do mundo real através do desenvolvimento de uma aplicação. Esta certificação exige que o candidato mostre certo nível de proficiência em alguns tópicos que não são contemplados em outras certificações, como RMI/Sockets ou Swing. Este artigo mostra o que os candidatos devem esperar ao optar por esta certificação, todos os passos que devem ser seguidos para obtê-la, as armadilhas que se escondem por trás dos requisitos e como seria a resolução de um exemplo fictício de atribuição. A Roberto Perillo ([email protected]) é bacharel em Ciência da Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com Java há quase 5 anos, possui as certificações SCJP, SCWCD e SCJD, e é moderador no JavaRanch, onde participa ativamente dos fóruns. Já trabalhou com JEE em grandes empresas, como Avaya e IBM, onde, nesta última, foi co-fundador do time de inovação de ibm.com GWPS LA. Desmistificando a Certificação SCJD A certificação que merece mais atenção da comunidade Java

Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

  • Upload
    others

  • View
    3

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

62 www.mundoj.com.br62626262 wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww.mu.mu.mu.mu.mu.mu.mu.mu.mu.mu.mu.mu.mundondondondondondondondondondondondondondondondondondondoj.cj.cj.cj.cj.cj.cj.cj.cj.cj.cj.cj.cj.cj.cj.cj.com.om.om.om.om.om.om.om.om.om.om.om.om.om.om.om.om.om.om.om.om.brbrbrbrbrbrbrbrbrbrbrbrbrbrbr

tualmente, a linguagem Java possui oito certificações, sendo que apenas duas delas são baseadas em desempenho prático, ou seja, o candidato é submetido a uma tarefa e só consegue êxito se desenvolvê-la e atingir um número mínimo de pontos

após avaliação. Uma destas certificações é a SCJD (Sun Certified Java De-veloper), na qual o candidato precisa desenvolver uma atribuição de pro-gramação e um exame de redação referente ao programa desenvolvido. Na atribuição, o candidato deve construir um programa cliente/servidor que precisa seguir algumas regras e também deve preparar documentos referentes à própria atribuição. No exame de redação, o candidato deve responder a quatro perguntas genéricas (ou que se aplicam a qualquer um dos modelos de atribuição). Embora as perguntas sejam referentes a pontos específicos do projeto, o real objetivo deste exame é verificar se o projeto foi realmente desenvolvido pelo candidato que solicitou a certifi-cação, e não por outra pessoa. Vale lembrar que tudo, desde comentários JavaDoc à prova de redação, deve ser escrito em inglês.

a r t i g o

De todas as certificações Java disponíveis

atualmente, a SCJD é a única que efetivamente

exige que o candidato mostre na prática que sabe

utilizar a linguagem Java para resolver problemas

do mundo real através do desenvolvimento de

uma aplicação. Esta certificação exige que o

candidato mostre certo nível de proficiência

em alguns tópicos que não são contemplados

em outras certificações, como RMI/Sockets ou

Swing. Este artigo mostra o que os candidatos

devem esperar ao optar por esta certificação,

todos os passos que devem ser seguidos para

obtê-la, as armadilhas que se escondem por trás

dos requisitos e como seria a resolução de um

exemplo fictício de atribuição.

A

Roberto Perillo

([email protected]) é bacharel em Ciência da

Computação e está atualmente concluindo o curso

de especialização em Engenharia de Software do

ITA. Trabalha com Java há quase 5 anos, possui as

certificações SCJP, SCWCD e SCJD, e é moderador no

JavaRanch, onde participa ativamente dos fóruns. Já

trabalhou com JEE em grandes empresas, como Avaya

e IBM, onde, nesta última, foi co-fundador do time de

inovação de ibm.com GWPS LA.

Desmistificando a Certificação SCJD

A certificação que merece mais atenção da comunidade Java

Page 2: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

63

Para obter esta certificação, o candidato deve obrigatoriamente ter qual-quer versão da certificação SCJP, e como ela é composta de duas fases, são necessários dois vouchers para concluí-la. Ao solicitar a certificação, o candidato recebe um arquivo .jar que contém um arquivo chamado instructions.html e um arquivo .db, que representa um banco de dados não-relacional. O arquivo instructions.html contém todas as instruções e regras necessárias para o desenvolvimento do projeto, e contém também o código de uma interface Java que deve ser implementada por uma classe que deve ter um nome específico, indicado nas instruções. Esta classe será responsável pelo acesso ao arquivo .db recebido no arquivo .jar. Após desenvolver o projeto e todos os documentos solicitados, o candidato deve fazer o upload do projeto resolvido e fazer o exame de redação. O resultado é disponibilizado ao candidato entre 4 a 6 semanas. O prazo máximo para o candidato fazer o upload do projeto resolvido é de um ano a partir da data do download da atribuição, e a pontuação

Este artigo está organizado da seguinte forma: primeiramente, é feita uma análise sobre como é a atribuição real. Em seguida, são mostradas técnicas e padrões que podem ser aplicados na resolução da atribuição, e também as etapas que podem ser seguidas durante a resolução, com exemplos de como cada componente pode ser desenvolvido. Em se-guida, para reforçar o entendimento, são abordados os modos como a aplicação deve ser executada. Em seguida, é dada a visão final do projeto, detalhando todos os artefatos que devem ser entregues ao final da reso-lução do projeto. O mecanismo de bloqueio, que é o item mais complexo desta certificação, é abordado em seguida. E, finalmente, são feitas as considerações finais, finalizando o artigo.

A atribuição

Scarper, sendo que a essência de cada projeto é sempre a mesma, apenas

com algumas pequenas variações entre suas versões. Basicamente, o de-

senvolvimento consiste na construção de um sistema cliente/servidor que

acessa um arquivo binário (o formato do arquivo é fornecido junto com

as instruções). Este sistema deve permitir que o usuário reserve recursos

disponíveis, além de permitir que o usuário faça buscas de acordo com

determinados critérios. A atribuição não possui regras de negócio muito

complexas, mas o candidato deverá construir tanto o servidor (utilizando

RMI ou Sockets) quanto o cliente que acessa esse servidor. O servidor deve

ser capaz de lidar com múltiplas requisições simultâneas, e deve gerenciar o

acesso ao arquivo binário através de um mecanismo de bloqueio, de forma

a proteger a integridade do arquivo, para que um cliente não sobrescreva

inapropriadamente os dados providos por outro cliente.

Além disso, a aplicação também deve ser executada em modo “standalo-

ne”, o que significa que a aplicação deve disponibilizar as telas ao usuário

e acessar o arquivo binário localmente sem utilizar o código do servidor.

Em relação às telas, devem ser construídas utilizando-se Swing, e podem

utilizar AWT somente quando não houver componentes Swing corres-

pondentes. Por exemplo, o candidato deve utilizar javax.swing.JButton ao

invés de java.awt.Button, e pode também utilizar java.awt.event.ActionLis-

tener. As telas que o candidato deve construir não são complexas, devendo

apenas permitir que o usuário proveja dados de configuração, visualize os

registros do arquivo binário e efetue buscas e reservas. Assim, o candidato

deve construir telas Swing simples, criar um servidor capaz de lidar com

requisições simultâneas e gerenciar o acesso ao arquivo binário através de

um mecanismo de bloqueio e deve também criar o cliente que conecta a este servidor. O que define se os dados devem ser buscados local ou remotamente é um parâmetro passado na linha de comando quando a aplicação é executada.

Durante o desenvolvimento, o candidato se depara com várias perguntas, pelo fato de que as instruções fornecidas pela Sun possuem alguns pontos ambíguos. Embora possa parecer ao candidato que esteja faltando algu-ma informação, a Sun deixa algumas questões em aberto propositalmente. No final do documento de instruções, consta uma frase que diz que as ins-truções “deliberadamente deixam algumas questões não-especificadas, e alguns problemas escondidos”. A habilidade que o candidato tem de tomar decisões perante estas dúvidas, lidar com requisitos mal especificados e definir soluções com as opções disponíveis é parte da avaliação.

O candidato deve também ser capaz de filtrar o que deve ser feito e o que não precisa ser feito. Por exemplo, o candidato pode se perguntar se é necessário utilizar o padrão Observer, para que quando a aplicação seja executada em modo cliente (conectada ao servidor) e um registro seja modificado, todos os clientes sejam notificados. Este é um exemplo de um requisito que não é preciso implementar. Assim, o principal critério de avaliação é verificar se o candidato é capaz de fazer o que se pede de forma simples, sendo que o candidato não ganha pontos extras ao implementar

até perder pontos. Vale lembrar que é preferível que o candidato utilize implementações da API Java ao invés de suas próprias implementações.

Uma das primeiras coisas especificadas no documento de instruções é o texto que identifica o que seria a necessidade do usuário. O exemplo a seguir, apesar de fictício, mostra o que o candidato pode esperar na atri-buição real:

território nacional. Uma de suas áreas organiza excursões de ônibus por cidades em todo o Brasil. Uma nova lei do governo demandou um cadastro único de excursões no país e, dessa forma, todas as companhias que orga-nizam excursões deverão obrigatoriamente registrá-las nesse cadastro. A

criar esse cadastro. Na primeira fase, a ideia é que o sistema seja executado somente na rede da empresa, onde os representantes de venda o aces-sarão em um servidor local e, dessa forma, as empresas terão que entrar em contato para registrar suas excursões por telefone, mas em um prazo a ser definido, o sistema será migrado para web, onde as próprias empresas poderão acessá-lo e registrar suas excursões.

Para isso, o CIO da empresa decidiu contratar o desenvolvimento de um novo sistema em Java. Neste momento, a ideia é fazer algo somente para a primei-ra fase, pois a migração para web ainda não tem previsão. Dessa forma, não é previsto se os componentes desenvolvidos serão reutilizados no sistema web, mas talvez o sejam. Um dos requisitos do novo cadastro é que os dados sejam armazenados em um arquivo .txt, cujo formato foi fornecido pelo pró-prio governo. Dessa forma, não é possível utilizar um SGBD, como MySQL, por exemplo. Assim, terá que ser desenvolvido também um componente que acesse esse arquivo, respeitando o formato criado pelo governo.

A nova aplicação, utilizando o formato de arquivo existente, deve permitir que os representantes de vendas criem dados de novas excursões, efetuem exclusões e exibam os dados de uma determinada viagem. Este é o projeto cujo desenvolvimento lhe foi atribuído.

Page 3: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

64 www.mundoj.com.br

Além da construção do sistema, o candidato deve listar em arquivo chamado choices.txt todas as escolhas de arquitetura e projeto feitas durante o desen-volvimento. Neste arquivo, o candidato deve também listar pontos ambíguos encontrados nos requisitos, as escolhas correspondentes e os motivos pelos quais o candidato tomou as respectivas decisões. Embora a Sun recomende que se utilize um conjunto de convenções de código durante todo o pro-jeto, o candidato deve preferivelmente utilizar as convenções de código da própria Sun, e todos os elementos (pelo menos da interface pública) devem obrigatoriamente possuir comentários JavaDoc. Uma boa dica é fornecer comentários JavaDoc para todos os elementos de cada classe.

Com JavaDoc comentando corretamente os elementos das classes, o can-didato deve utilizar a ferramenta javadoc para gerar a documentação Java-Doc, que também deve obrigatoriamente ser entregue. O candidato deve também criar um guia do usuário para o projeto, que deve ser fornecido em um arquivo .txt, em formato HTML ou dentro da própria aplicação. O conteúdo deste guia deve conter as informações suficientes para que uma pessoa familiarizada com o propósito do projeto possa utilizar o sistema. Outro documento que deve ser entregue também é um arquivo chamado version.txt, no qual o candidato simplesmente indica a versão da JDK utili-zada e o sistema operacional em que a aplicação foi construída.

Resolvendo a atribuição

Existem alguns padrões de arquitetura que podem ser seguidos para que a atribuição possa ser resolvida. Uma boa ideia é manter as coisas o mais simples possível, pois a arquitetura definida e o quão fácil é utilizar e manter o código desenvolvido também é parte da avaliação. No excelente livro EJB 3 In Action, os autores abordam um estilo de arquitetura chamado “arquitetura tradicional de quatro camadas”, que é bastante conhecido entre designers de aplicações enterprise. Embora a aplicação a ser desenvolvida seja cliente/servidor, esse padrão pode ser seguido para a resolução da atribuição.

As quatro camadas que compõem este estilo de arquitetura são: camada de apresentação, camada de negócios, camada de persistência e camada de ban-

por lidar com interesses de UI (User Interface), como exibir mensagens visuais para o usuário, habilitar ou desativar campos-texto, lidar com botões do tipo “radio” etc. Esta camada deve conter somente lógica de UI, e não propriamente de negócios, que deve se concentrar somente na camada de negócios. A cama-da de negócios representa o coração deste estilo de arquitetura e é responsável por concentrar todas as regras de negócio implementadas pela aplicação. Esta

disso, todas as operações de banco de dados são intermediadas pela camada de persistência, que é uma abstração orientada a objetos sobre a camada de banco de dados. Esta camada não deve conter regras de negócio, e deve “bur-ramente” obedecer a todas as requisições da camada de negócio.

Apresentação

Negócios

Persistência

Base de Dados

As camadas da arquitetura apresentada são lógicas, o que significa que elas podem estar fisicamente agrupadas (no mesmo .jar ou projeto, por exemplo). O grande benefício da separação da aplicação em camadas é que cada cama-da cuida apenas de um aspecto específico, promovendo reúso, flexibilidade e facilidade na manutenção. Ao optar pela arquitetura em camadas, deve-se ter em mente que os componentes de uma camada precisam somente trocar mensagens entre si ou com as camadas “abaixo” (caso uma camada abaixo precise se comunicar com uma camada acima, pode-se utilizar mecanismos de callback ou o padrão Observer). Projetar e definir a API de uma camada de forma isolada ajuda a mantê-la simples e flexível. Por exemplo, pode-se definir os componentes da camada de negócios sem se preocupar se estes com-ponentes serão utilizados em uma aplicação web ou desktop. O quão fácil o código produzido é de evoluir e manter é um aspecto importante da avaliação.

No excelente livro Patterns of Enterprise Application Architecture, Martin Fowler aborda um padrão de organização de lógica de domínio chamado Transaction Script, cuja chave é a simplicidade. Este padrão organiza a lógica de negócios através de um conjunto de transaction scripts, sendo que transaction script é um método que trata de uma requisição da camada de apresentação e organiza a lógica de negócios, efetuando computações e podendo até acessar o banco de dados através de DAOs. Este padrão promove uma estrutura um pouco mais procedural, porque normalmente as classes tendem a ter uma granularidade mais grossa e os dados ficam separados do comportamento.

Um exemplo da aplicação do padrão Transaction Script é a própria estrutura proposta pela plataforma EJB (embora seja possível utilizar outros padrões de organização de lógica de domínio, o mais natural é que a implementação seja através de transaction scripts), onde os comportamentos são implementados nos métodos de negócios dos session beans, que utilizam objetos que são na maioria das vezes “burros” para se comunicar com o banco de dados e com a camada de apresentação (os chamados Transfer Objects, ou Data Transfer Objects). Embora possa parecer ruim, a atribuição proposta pela certificação pode ser resolvida utilizando este padrão. No documento de instruções, consta uma frase que diz que “um design claro, que pode ser entendido facilmente por programadores juniores, será preferido a um design complexo, mesmo que este seja um pouco mais eficiente”.

Em outro excelente livro, chamado POJOs In Action, Chris Richardson mostra como aplicar na prática o padrão Domain Model, que é outro padrão de organização de lógica de domínio. O Domain Model é o maior produto do Domain-Driven Design e, ao contrário do padrão Transaction Script, propõe implementar um modelo de domínio através de um modelo de objetos rico que utiliza plenamente todos os benefícios que a orientação a objetos oferece. A maioria das classes que compõe este modelo de objetos possui tanto dados quanto comportamentos que implementam as regras de negócio. Outras clas-ses (chamadas de serviços neste padrão) oferecem em suas APIs os serviços que devem ser realizados pela aplicação, e estes delegam a realização das tarefas para as entidades. Este modelo de domínio pode ser envolvido por Façades, que encapsulam a camada de domínio e lidam com tarefas de infra-estrutura, como delimitar as transações ou desanexar os objetos de domínio dos frameworks de persistência.

Aparentemente, utilizar o padrão Domain Model pode ser melhor do que uti-lizar o padrão Transaction Script para organizar a lógica de domínio da atribui-ção. Mas Chris Richardson também faz considerações sobre onde faz sentido utilizar o padrão Transaction Script, “como quando o time de desenvolvimento não possuir as habilidades de projeto OO necessárias para desenvolver um modelo de domínio, ou a lógica de negócios for muito simples. Também não faz sentido usar um modelo de domínio quando não for possível utilizar um

Figura 1. Organização da arquitetura tradicional de quatro camadas.

Page 4: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

65

Primeiro passo: implementação da classe de acesso a dados

framework de persistência”. Analisando tanto o texto das instruções quanto as considerações de Chris Richardson, pode-se perceber que pode ser mais adequado utilizar o padrão Transaction Script para resolver a atribuição. Dessa forma, pode-se utilizar a arquitetura tradicional em quatro camadas e construir a camada de negócios com Transaction Scripts, para que cada transaction script lide com uma requisição da camada de apresentação.

Definidos os padrões de arquitetura a serem seguidos, podemos começar a pensar efetivamente na implementação. Embora em uma arquitetura bem definida não faça diferença por qual das camadas a aplicação come-ce a ser implementada, um bom lugar para iniciar o desenvolvimento do projeto é a classe que deve implementar a interface provida pela Sun no documento de instruções. Importante: esta classe deve obrigatoriamen-te possuir um nome específico (indicado no documento de instruções) e deve também obrigatoriamente estar dentro de um pacote específico (também indicado no documento de instruções), ou o candidato será reprovado automaticamente caso não obedeça a estas regras. Esta classe é responsável pelo acesso ao arquivo .db fornecido pela Sun. A interface fornecida pela Sun é limitada em alguns aspectos, porém como o candi-dato lida com estas limitações também é parte da avaliação. A Listagem

no documento de instruções.

package certification.database;

public interface DB {

// Creates a new record in the database.

public void createExcursion(Excursion excursion)

throws DuplicateExcursionException;

// Updates the fields of a given excursion.

public void updateExcursion(long recordNumber, Excursion excursion)

throws ExcursionNotFoundException;

// Deletes a particular record.

public void deleteExcursion(long recordNumber)

throws ExcursionNotFoundException;

// Reads a record from the database.

public Excursion readExcursion(long recordNumber)

throws ExcursionNotFoundException;

// Locks a particular record, so that it can only be

// updated or deleted by this client.

public void lock(long recordNumber)

throws ExcursionNotFoundException;

// Unlocks a particular record.

public void unlock(long recordNumber)

throws ExcursionNotFoundException;

// Other methods...

}

package certification.domain;

import java.util.Date;

public class Excursion implements Serializable {

// Número do ônibus

private int busNumber;

// Nome da empresa

private String companyName;

// Data de saída da cidade de origem

private Date departure;

// Data da volta à cidade de origem

private Date arrival;

// Nome da cidade de origem

private String origin;

// Nome da cidade de destino

private String destiny;

// Preço da excursão

private String price;

// Capacidade de passageiros no ônibus

private byte capacity;

// Getters e Setters omitidos...

}

é a interface fornecida pela Sun. A interface real não utiliza coleções como List, ou algo como a classe Excursion, por exemplo. Na atribuição real, os dados de um registro são representados por vetores de String. Esta interface reflete dois aspectos importantes: o gerenciamento dos dados e o gerenciamento do me-canismo de bloqueio. É importante que todos os métodos sejam corretamente implementados (mesmo se não forem ser utilizados em nenhuma regra de ne-gócio implementada na atribuição). Além de ser mandatório, trata-se de uma questão de reusabilidade. Levando em consideração o exemplo da Listagem

reutilizar a implementação criada aqui. Neste exemplo, um dos requisitos é que o arquivo de dados seja um arquivo-texto com um formato fornecido pelo governo. Na atribuição real, o arquivo de dados é um arquivo binário, que pode ser lido utilizando-se java.io.RandomAccessFile. No FAQ do fórum da certifica-ção SCJD do JavaRanch pode-se encontrar um código feito pelo autor deste artigo que lê e exibe no console o conteúdo do arquivo binário.

Os candidatos podem então utilizá-lo e entender como o arquivo pode ser lido e criar suas próprias implementações. Na atribuição real, alguns métodos da interface provida pela Sun recebem vetores de String, onde cada valor do vetor representa um campo do registro no banco de dados. O formato do arquivo também é fornecido no documento de instruções, e contém informações como um valor que indica se o arquivo é válido ou não, tamanho em bytes de cada registro, número de campos em cada registro etc. O esquema do banco de dados também é fornecido, e contém informações como o nome e o tamanho em bytes de cada campo. Neste exemplo, a classe Excursion reflete exatamente o formato de um registro do banco de dados.

Page 5: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

66 www.mundoj.com.br

No caso do método de criação de registro, o candidato terá que avaliar se no esquema fornecido é possível criar um registro com chave duplicada. Para isso, terá que verificar se algum campo ou combinação de campos podem ser considerados únicos. Em caso afirmativo, o método lançará essa exceção, mas, na maioria das atribuições, nenhum campo ou combinação de campos pode ser considerado único. Neste exemplo, a combinação dos campos bus-Number, company e departure pode ser considerada única (considerando que existe uma remota possibilidade de duas empresas terem dois ônibus com o mesmo número), então pode-se extrair esses campos da classe Excursion e movê-los para uma classe chamada ExcursionPrimaryKey, e se já existir outro registro com esses dados, o método de criação de registros lançará DuplicateExcursionException.

Outro detalhe importante é que todas as exceções que podem ser lança-das pelos métodos da interface devem ser criadas com os construtores indicados na atribuição. Pode-se criar uma hierarquia de exceções, onde as exceções a serem criadas estendem outra exceção de mais alto nível. Pode-se criar uma exceção chamada DatabaseException, por exemplo, que estende diretamente a classe java.lang.Exception, e tornar as exceções lan-çadas pelos métodos da interface subclasses da classe DatabaseException. Isso facilita o tratamento de exceções, pois, se um método precisar lançar várias exceções que sejam subclasses de DatabaseException, este método pode declarar na cláusula throws somente a exceção DatabaseException.

package certification.database;

public class DatabaseException extends Exception {…}

package certification.database;

public class ExcursionNotFoundException extends DatabaseException {…}

package certification.database;

public class DuplicateExcursionException extends DatabaseException {…}

package certification.database;

public interface LocalDB extends DB {

public void saveData() throws DatabaseException;

}

package certification.database;

public class Database implements LocalDB {

private static final LocalDB INSTANCE = new Database();

// Cache de registros em memória

private static final Map<Long, Excursion> data =

new HashMap<Long, Excursion>();

// Caminho do banco, utilizado para reescrever os

// registros quando a aplicação é encerrada

private static String databasePath;

private Database() {

super();

}

public static synchronized LocalDB getInstance(String dbPath)

throws DatabaseException {

// Lê os dados do arquivo informado e os coloca no objeto

// data, podendo lançar DatabaseException

readData(dbPath);

databasePath = dbPath;

return INSTANCE;

}

public synchronized void createExcursion(Excursion excursion)

throws DuplicateExcursionException {…}

// Implementação dos outros métodos omitida…

}

Listagem 3. Definição das exceções.

Listagem 4. Extensão da interface de acesso a dados.

Listagem 5. Implementação da interface de acesso a dados.

Pode-se criar um Façade que implemente a interface provida pela Sun e dele-gue as tarefas de gerenciamento de dados e gerenciamento do mecanismo de bloqueio para outras classes. Para manter as coisas simples, a implementação da interface DB neste artigo será um Singleton que cuidará destes dois aspec-tos. O acesso aos dados pode ser feito de duas formas: lendo o arquivo em disco a cada requisição ou acessando os dados em um cache de registros em memória. Caso o candidato opte por ler o arquivo a cada requisição, terá que assegurar que o arquivo não perca sua integridade a cada requisição, e que os dados não fiquem “bagunçados”. A abordagem mais fácil é ler todo o arquivo no momento em que a aplicação é executada e colocar todos os registros em um cache em memória. Alguns candidatos dizem que isso pode consumir mui-ta memória caso o banco de dados cresça. Mas o candidato pode argumentar que, além de ser a opção mais fácil, o banco de dados não é grande, e também não é informado se o banco de dados pode crescer, então o candidato pode assumir com tranquilidade que essa escolha não será um problema.

Ao optar por armazenar os registros em cache, deve-se decidir quando os regis-tros serão colocados em memória e quando serão escritos de volta ao arquivo em disco. Como o objeto de acesso ao banco é um Singleton, uma opção é ler os dados na primeira chamada ao método estático getInstance() e colocá-los em uma estrutura de dados, como Map<Long, Excursion>, por exemplo. Embora fora definida nesse exemplo uma chave primária, a própria interface fornecida pela Sun utiliza nos métodos o número de cada registro, então se torna mais fácil recuperar registros por seus respectivos números. Assim, dado

um número, é fácil obter o registro correspondente, através do método get() da interface Map. Pode-se também definir um novo método na classe que implementa a interface fornecida pela Sun para a escrita dos dados de volta ao arquivo, no momento em que a aplicação é encerrada. O problema é que esse método não faz parte da interface fornecida pela Sun (que não deve ser alterada), então não é possível acessá-lo utilizando a interface como referência. É possível acessar os métodos utilizando a própria classe de implementação como referência, porém deve-se programar para interfaces sempre que possí-vel. A solução é criar uma interface que estenda a interface fornecida pela Sun e colocar o novo método (e também outros métodos que o candidato julgue necessário). Dessa forma, a interface fornecida pela Sun é implementada e o candidato consegue driblar parte de suas limitações. A Listagem 4 mostra como a interface fornecida pela Sun pode ser estendida, e a Listagem 5 mostra como a nova interface pode ser implementada. Uma boa dica é começar a escrever os comentários JavaDoc logo no início da construção da aplicação, para facilitar o entendimento do próprio candidato e para não deixar todo este trabalho para o final.

Page 6: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

67

package certification.business;

// imports omitidos…

public interface ExcursionServices {

public void createExcursion(Excursion excursion) throws RemoteException,

ServicesException;

public void deleteExcursion(long recordNumber)

throws RemoteException, ServicesException;

public Excursion readExcursion(ExcursionPrimaryKey key)

throws RemoteException, ServicesException;

public Map<Long, Excursion> getAllRecords()

throws RemoteException;

public void saveData() throws RemoteException,

ServicesException;

}

Listagem 6. API do componente de negócios.

A classe de implementação da interface fornecida pela Sun deve obri-gatoriamente ser Thread-safe, então, uma opção é definir todos os métodos como “synchronized”. No exemplo da Listagem 5, esta classe é um Singleton, portanto, somente um método poderá ser acessado por vez, e não existirão outras classes lidando com os dados do banco de dados. Como alguns problemas poderão ocorrer ao gravar os dados (por exemplo, o arquivo indicado no momento que a aplicação foi executada pode ter sido deletado ou substituído), então o método pode lançar a exceção DatabaseException (a classe-mãe da hierarquia de exceções, como mostrado na Listagem 3).

Para a próxima etapa, pode-se definir os métodos de negócios que serão utilizados pela interface gráfica. A aplicação deve ser executada tanto em modo cliente (ou seja, conectada a um servidor, de onde as informações são obtidas e para onde são enviadas) quanto em modo “standalone” (ou seja, as informações são obtidas do banco de dados local, sem utilizar o código do servidor). Para promover abstração e flexibilidade, pode-se definir uma inter-face, na qual uma implementação busca as informações do servidor, e outra implementação busca as informações localmente. Importante: em modo “standalone”, o código do servidor não deve ser utilizado, caso contrário, o candidato será reprovado automaticamente.

Neste ponto, é importante definir se queremos implementar um “thick client” ou um “thin client”. A diferença entre esses dois conceitos é bem simples. No caso do “thick client”, a lógica de negócios é implementada no lado do cliente, e no caso do “thin client”, a lógica de negócios é implemen-tada no lado do servidor. O código do servidor pode utilizar RMI ou So-ckets. Importante: caso o candidato opte por RMI, deverá utilizar somente RMI, ou se optar por Sockets, deverá utilizar somente Sockets. Além disso, caso o candidato opte por RMI, deverá atentar a algumas restrições da utilização de RMI descritas nas instruções. Caso o candidato não siga essas regras, será reprovado automaticamente. Para manter as coisas simples, o exemplo deste artigo utiliza código RMI.

Uma vantagem do “thin client” é que a API utilizada no lado cliente fica mais simples, assim como o código do próprio servidor. No caso do “thick client”, para que as regras de negócio sejam corretamente implementadas no lado do cliente, a API do servidor normalmente possui mais métodos do que no caso do “thin client”. Além disso, no caso do “thick client”, as transa-ções também precisam ser controladas no lado do cliente, pois a transação é iniciada e finalizada no lado do cliente. Isso quer dizer que, se o cliente “travar” entre o início e o fim da transação, a transação nunca é finalizada. No caso do “thin client”, a implementação do mecanismo de bloqueio também se torna mais fácil. Para manter as coisas simples, o exemplo será implementado utilizando “thin client”.

Como a camada de negócios será organizada através de um conjunto de transaction scripts, nesta etapa é importante identificá-los. Existem algumas técnicas que podem ser utilizadas para identificá-los em um cenário real. Uma forma é analisar os casos de uso, pois transaction scripts geralmente correspondem aos passos de um caso de uso. Outra forma é analisar as funcionalidades oferecidas pela UI e definir um transaction script para cada tipo de requisição. A atribuição em si não contempla casos de uso, porém fornece todas as funcionalidades que deverão ser oferecidas na tela principal do projeto. Dessa forma, o candidato pode analisar essas funcionalidades e definir um método na camada de negócios para cada funcionalidade.

Segundo passo: definição e implementação dos métodos de negócios

Por exemplo, consideremos que os requisitos e as funcionalidades que deverão ser disponibilizados na UI são:

-bus, o nome de uma empresa e uma data de saída.

Na atribuição real, não é necessário criar ou excluir registros, e nem criar uma funcionalidade que especificamente exiba os dados de um registro (sendo assim exemplos de funcionalidades que não precisam ser implementadas), mas como este artigo busca apenas instruir os leitores a resolverem com sucesso a atribuição, justamente estas funcionalidades serão construídas.

Analisando as funcionalidades a serem oferecidas pela camada de apresen-tação, podemos identificar um método para a criação de registros, outro para a exclusão de registros, outro para ler os dados de uma excursão específica e outro para recuperar os registros do banco de dados, para exibi-los na JTable. Além disso, como os dados do arquivo que representa o banco de dados serão armazenados em memória, também é preciso definir um método que escreva os registros de volta ao arquivo físico quando a aplicação for finalizada. Estes métodos também deverão estar disponíveis remotamente (neste exemplo, através de um servidor RMI), então também deverão incluir em suas cláusulas throws a exceção java.rmi.RemoteException para que isso seja possível. Assim, pode-se definir a API do componente de negócios da seguinte forma:

A interface da Listagem 6 possuirá duas implementações: uma para o modo cliente e outra para o modo “standalone”. No caso do modo “standalone”, a camada de apresentação acessará a implementação desta interface, e esta implementação acessará diretamente a classe de acesso a dados e, no modo cliente, a camada de apresentação acessará o servidor (a implementação desta interface será o servidor propriamente dito), e o servidor acessará a classe de acesso a dados. Como os objetos de domínio transitarão de uma VM para outra

Page 7: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

68 www.mundoj.com.br

através da rede, é importante que estes objetos implementem a interface java.io.Serializable. No caso da interface definida na Listagem 6, as classes Excursion e ExcursionPrimaryKey deverão implementar a interface java.io.Serializable.

Quanto mais abstração, mais fácil se torna manter e evoluir um sistema orien-tado a objetos. Definir as APIs de forma independente (sem se preocupar com quem os utilizará e de quais APIs o componente dependerá) também

-ção acessará a camada de negócios através da interface definida na Listagem 6. Dessa forma, a camada de apresentação não saberá se os dados são locais ou remotos. Para que tudo funcione corretamente, é necessário construir o código que arranje estes objetos e os conecte. Por exemplo, pode-se cons-truir um código que inicialize a implementação da camada de negócios e a forneça para a camada de apresentação. Como foi mencionado anterior-mente, o que define se os dados devem ser obtidos local ou remotamente é um parâmetro na linha de comando quando a aplicação é executada. Assim, pode-se verificar esse parâmetro ao iniciar a aplicação, instanciar o compo-nente de negócios e fornecê-lo à camada de apresentação.

package certification.business;

// imports omitidos...

public class DefaultExcursionServices implements ExcursionServices {

private LocalDB dbManager;

public DefaultExcursionServices(LocalDB dbManager) {

super();

this.dbManager = dbManager;

}

public void saveData() throws ServicesException {

try {

dbManager.saveData();

} catch (DatabaseException exception) {

String message = “It was not possible to save the data “

+ “back to the file due to the following reason: ”

+ exception.getMessage();

throw new ServicesException(message, exception);

}

}

public void createExcursion(Excursion excursion)

throws ServicesException {...}

// Implementação dos outros métodos omitida...

}

package certification.business;

// Imports omitidos...

public interface RemoteExcursionServices

extends Remote, ExcursionServices {

public static final String SERVER_NAME = “ExcursionServer”;

public abstract void startServer(int port) throws RemoteException;

}

package certification.business;

// imports omitidos...

public class RMIRemoteExcursionServices extends DefaultExcursionServices

implements RemoteExcursionServices {

public RMIRemoteExcursionServices(LocalDB dbManager) {

super(dbManager);

}

public void startServer(int port) throws RemoteException {

RemoteExcursionServices server = (RemoteExcursionServices)

UnicastRemoteObject

.exportObject(this, 0);

Registry registry = LocateRegistry.createRegistry(port);

registry.rebind(SERVER_NAME, server);

Runtime.getRuntime().addShutdownHook(new Thread() {

public void run() {

final String message = “Saving database changes, please “

+ “wait...”;

System.out.println(message);

try {

saveData();

} catch (ServicesException exception) {

String message = “The database file could not be updated

+ “ with the latest changes due to the following reason: “

+ exception.getMessage()

+ “. Please contact the system administrator for “

+ “assistence.”;

System.err.println(message);

}

}

});

String message = “Server running on port #” + port

+ “\nPress Ctrl + C to exit...”;

System.out.println(message);

}

}

Listagem 7. Implementação padrão do componente de negócios.

Listagem 8. Definição da API do servidor.

Listagem 9. Implementação do servidor RMI.

A disponibilização dos serviços remotos será implementada utilizando RMI, portanto, deve-se definir uma interface que estenda java.rmi.Remote, pois esta implementação será o servidor propriamente dito. Neste caso, é preciso que todos os métodos da interface declarem que podem lançar java.rmi.Re-moteException. Como os métodos da interface ExcursionServices já incluem em suas cláusulas throws essa exceção, pode-se definir uma nova interface que estenda tanto certification.business.ExcursionServices quanto java.rmi.Remote. Pode-se também definir um método que seja responsável por iniciar o servidor, que será invocado quando a aplicação for inicializada em modo servidor. Este método pode receber um int, que corresponderá à porta onde

o serviço será disponibilizado no servidor. Para manter as coisas simples, este exemplo também utiliza um shutdown hook, que é uma Thread disparada no

Page 8: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

69

package certification.ui.launch;

// imports omitidos...

public abstract class OKButtonListener implements ActionListener {

protected abstract void verifyData() throws ValidationException;

protected abstract ExcursionServices getService() throws LaunchException;

protected abstract void saveProperties() throws IOException;

public void actionPerformed(ActionEvent event) {

try {

verifyData();

saveProperties();

new MainWindow(getService());

} catch (ValidationException exception) {

String message = exception.getMessage();

JOptionPane.showMessageDialog(null, message, “Excursions System”,

JOptionPane.ERROR_MESSAGE);

} catch (IOException exception) {

String message = “The properties could not be saved.”;

JOptionPane.showMessageDialog(null, message, “Excursions System”,

JOptionPane.ERROR_MESSAGE);

} catch (LaunchException exception) {

String message = “A problem occurred. Please verify the parameters “

+ “provided and try again.”;

JOptionPane.showMessageDialog(null, message, “Excursions System”,

JOptionPane.ERROR_MESSAGE);

}

}

}

Listagem 10. Listener do botão OK das telas de propriedades dos modos

cliente e “standalone”.

Um detalhe a ser observado na implementação proposta para os com-

negócios intermedeia a comunicação da camada de apresentação com a camada de persistência. Assim, os construtores dos componentes de negócio recebem como parâmetro a interface definida na Listagem 4, que é uma forma de injeção de dependência que promove acoplamento fraco. Como neste caso os componentes de negócio dependem somente de um objeto, definir estes construtores facilita a utilização destes com-ponentes, pois assim que forem construídos, estarão prontos para serem utilizados sem a necessidade de chamar métodos “setter” extras. Além disso, torna-se fácil utilizá-los em frameworks que oferecem injeção de dependência, como o Spring, por exemplo. No caso da atribuição real, o arranjo destes objetos pode ser feito no momento em que a aplicação é inicializada, dado o modo em que a aplicação está sendo executada.

Com o componente da camada de persistência implementado e os com-ponentes da camada de negócios implementados, podemos passar para o terceiro passo: a implementação das telas propriamente ditas.

Terceiro passo: implementação das telas

Depois de ter os outros componentes implementados, podemos agora desenvolver o código referente às telas. Na atribuição real, são fornecidas todas as funcionalidades e componentes que devem obrigatoriamente estar presentes na tela principal. No momento em que a aplicação é executada, deve ser exibida uma tela onde o usuário possa informar as propriedades da aplicação de acordo com o modo em execução. Por exemplo, ao executar a aplicação em modo servidor, o usuário pode informar o caminho do arquivo que representa o banco de dados e em qual porta o servidor deve ser iniciado. Em modo “standalone”, o usuário pode informar o caminho do arquivo que representa o banco de dados e, em modo cliente, o usuário pode informar o IP do servidor e em qual porta no servidor os serviços estão disponíveis.

Os dados informados na tela de propriedades deverão obrigatoriamente ser salvos em um arquivo .properties, que deverá ser criado com um nome específico e em um local específico indicados nas instruções. Na primeira vez que a aplicação for executada, o arquivo naturalmente não existirá e, dessa forma, o arquivo deverá ser criado e os dados fornecidos pelo usuário deverão ser salvos nesse arquivo. A partir da segunda vez que a aplicação for executada em um determinado modo, os campos da tela de propriedades deverão ser populados com os dados previamente informados pelo usuário, caso a aplicação já tenha sido executada neste modo. Importante: caso os campos das telas de propriedades não sejam persistidos entre as execuções da aplicação, o candidato será reprovado automaticamente.

A única coisa que varia entre os modos de execução cliente e “standalone” é a fonte dos dados. Executando a aplicação em modo cliente, os dados serão fornecidos pelo servidor (neste exemplo, implementado pela classe

servidor deve, obviamente, estar em execução para que os dados possam ser recuperados. Em modo “standalone”, os dados serão recuperados local-mente, e pode-se assim utilizar a classe DefaultExcursionServices, mostrada na Listagem 7. Os componentes utilizados nas telas de propriedades serão responsáveis por injetar uma instância da classe de negócios no objeto da tela principal, para que tudo funcione corretamente.

Em ambos os casos, os componentes de negócios a serem instanciados obedecem a API da interface ExcursionServices. Pode-se assim criar uma classe abstrata que implemente java.awt.event.ActionListener e que tenha

um método abstrato getService(), que retorna uma implementação de ExcursionServices. Essa classe abstrata possuirá uma implementação que instanciará RMIRemoteExcursionServices, que escutará o botão OK da tela de propriedades do modo cliente, e outra implementação que instanciará DefaultExcursionServices, que escutará o botão OK da tela de proprieda-des do modo “standalone”, caracterizando assim o padrão Factory Method. Como os dados das telas também precisam ser validados e salvos em um arquivo .properties, pode-se também definir métodos abstratos que executem essas funções, caracterizando assim o padrão Template Method. Como pode haver problemas, os métodos podem lançar exceções (que devem ser criadas). É ideal que as mensagens sejam internacionalizadas, através de um bundle de mensagens, mas, para facilitar este exemplo, as mensagens estão “hardcoded”.

Pode-se definir uma interface para cada tela de inserção de propriedades

e fornecer as implementações para os listeners que escutam o botão OK

de cada tela. Através das interfaces, os listeners têm acesso aos dados

providos pelo usuário e podem assim instanciar corretamente a classe de

negócios adequada e fornecê-la à tela principal.

Page 9: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

70 www.mundoj.com.br

package certification.ui.launch;

// imports omitidos...

public class StandaloneOKButtonListener extends OKButtonListener {

// A interface que é implementada pela tela onde o usuário

// informa as propriedades de inicialização em modo Standalone

private StandaloneLaunchManager manager;

public StandaloneOKButtonListener(StandaloneLaunchManager manager)

{

super();

this.manager = manager;

}

@Override

protected ExcursionServices getService() throws LaunchException {

String dbLocation = manager.getDbLocation();

try {

LocalDB db = Database.getInstance(dbLocation);

return new DefaultExcursionServices(db);

} catch (DatabaseException exception) {

throw new LaunchException(exception);

}

}

@Override

protected void saveProperties() throws IOException {...}

@Override

protected void verifyData() throws ValidationException {...}

}

Listagem 11. Listener do botão OK da tela de propriedades do modo

“standalone”.

Na atribuição real, a tela principal deve exibir os dados do arquivo binário em JTable, deve permitir que os usuários busquem por todos os registros ou por registros onde alguns dados sejam iguais a valores fornecidos pelo usuário e deve também permitir que o usuário reserve um determinado registro, atuali-zando assim o banco de dados apropriadamente. Para o requisito de reserva, o mecanismo de bloqueio deve obrigatoriamente ser utilizado, de forma a garantir que os dados fornecidos por um usuário não sobrescrevam inapro-priadamente os dados fornecidos por outro.

A tela a ser construída deve ser a mais amigável e de fácil utilização possível. Por exemplo, pode-se definir teclas de atalho para itens de menu ou botões. Além disso, na Seção de User Interface no documento de instruções, consta uma frase que diz que a UI deve ser preparada para futuras melhorias. Isso é uma dica de que gerenciadores de layout que facilitem a inserção de novos componentes, como BorderLayout, FlowLayout ou GridBagLayout, devem ser utilizados. Caso o candidato julgue necessário, pode bloquear a inserção de caracteres inválidos em determinados campos (como letras em um campo numérico), utilizar máscaras em campos de texto ou desabilitar botões caso a tela esteja em um determinado estado (por exemplo, desabilitar o botão de busca enquanto o critério de busca não tenha sido preenchido).

No exemplo proposto por este artigo, a aplicação deve permitir criar e excluir registros, e ler um registro, dado um número de ônibus, o nome de uma em-presa e uma data de saída. Assim, a tela principal pode ter o seguinte layout:

Figura 2. Layout da tela principal da aplicação.

No topo da página, é oferecida a funcionalidade de exibição dos dados de uma determinada excursão, que utiliza o método readExcursion() do compo-nente de negócios. Os dados do banco de dados são exibidos na JTable, cujo TableModel utiliza o método getAllRecords() do componente de negócios, e as funções de inclusão e exclusão de registros estão disponíveis abaixo da JTable, através dos botões “New Excursion” e “Delete Excursion”, que utilizam os métodos createExcursion() e deleteExcursion() do componente de negó-cios, respectivamente.

package certification.ui;

// imports omitidos...

// A interface MainWindowManager define métodos que recuperam dados

// informados na tela principal, como número do ônibus ou nome da

empresa

// para a exibição dos dados de uma determinada excursão.

public class MainWindow implements MainWindowManager {

// O JTextField onde o usuário informa o número do ônibus

private JTextField busNumber;

public MainWindow(ExcursionServices services) {

// A construção da tela é feita dentro deste construtor

super();

...

JButton showExcursion = new JButton(“Show Excursion”);

ActionListener showExcursionListener =

new ShowExcursionListener(this, services);

showExcursion.addActionListener(showExcursionListener);

}

public String getBusNumber() {

return busNumber.getText();

}

}

Na implementação da classe que representa a tela principal deste exemplo, de-ve-se definir um construtor que receba a interface ExcursionServices, de forma a promover polimorfismo. Dessa forma, a camada de apresentação executará suas funções, porém sem saber se os dados sendo manipulados são locais ou remotos. Assim, se no futuro houver outras fontes de dados, basta criar novas implementações da interface ExcursionServices, mostrada na Listagem 6, e fornecê-las à tela principal. A implementação dos serviços fornecida para a tela

Page 10: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

71

package certification.ui;

// imports omitidos...

public class ShowExcursionListener implements ActionListener {

private MainWindowManager manager;

private ExcursionServices services;

public ShowExcursionListener(MainWindowManager manager,

ExcursionServices services) {

super();

this.manager = manager;

this.services = services;

}

public void actionPerformed(ActionEvent event) {

String busNumber = manager.getBusNumber();

String companyName = manager.getCompanyName();

String departureString = manager.getDeparture();

Date departure = convertToDate(departureString);

ExcursionPrimaryKey key = new ExcursionPrimaryKey();

key.setBusNumber(Integer.parseInt(busNumber));

key.setCompany(companyName);

key.setDeparture(departure);

try {

Excursion excursion = services.readExcursion(key);

showExcursion(excursion);

} catch (ServicesException exception) {

String message = “No excursions could be found.”;

JOptionPane.showMessageDialog(null, message, “Excursions System”,

JOptionPane.ERROR_MESSAGE);

} catch (RemoteException exception) {

String message = “A problem occurred with the server.”;

JOptionPane.showMessageDialog(null, message, “Excursions System”,

JOptionPane.ERROR_MESSAGE);

}

}

private Date convertToDate(String departureString) {…}

private void showExcursion(Excursion excursion) {...}

}

é repassada para os ActionListeners que escutam os botões “New Excursion” e “Delete Excursion”, para que os métodos de serviços possam ser consumidos dentro destes listeners. Além disso, a implementação dos serviços também pode ser repassada para o TableModel que controla o estado da tabela, para que fique mais fácil organizar os registros sendo exibidos. No caso do listener do botão “Show Excursion”, é necessário passar também uma referência ao objeto da tela, para que ele possa recuperar os dados inseridos pelo usuário e chamar o método readExcursion() do serviço. Pode-se definir os construtores dos listeners de cada botão da tela principal de acordo com a função que cada um deve desempenhar.

ble, pode-se criar uma classe que estenda a classe javax.swing.table.Abs-tractTableModel e definir métodos que facilitem o controle do estado da tabela e recuperação de registros sendo exibidos. Pode-se, por exemplo, definir um método que recupere um objeto Excursion, dado o número da linha selecionada na tabela. Este TableModel pode também ter uma referência ao objeto de negócios para, por exemplo, recuperar todos os registros e construir o estado inicial da tabela.

Quarto passo: empacotando a aplicação

Modos de execução da aplicação

Depois de finalizar a implementação do projeto, deve-se empacotar a aplicação em um único arquivo .jar. Embora cada modo de execução utilize somente determinadas classes, todas as classes devem ser empacotadas dentro do mesmo arquivo .jar. Por exemplo, neste exemplo, em modo servidor, somente as interfaces e a classe de persistência, e as interfaces e o componente remoto de negócios são utilizados, então, somente essas classes seriam necessárias para que esse modo pudesse ser executado. Nor-malmente, esse arquivo deve se chamar runme.jar. Além disso, o candidato deve criar um arquivo MANIFEST.MF e colocá-lo dentro da pasta META-INF, na raiz do arquivo .jar, indicando a classe que possui o método main(), chamado quando a aplicação é executada. O método main() deve verificar o argumento que indica como a aplicação deve ser executada, passado na linha de comando, e chamar a tela de propriedades correspondente. Impor-tante: o único argumento permitido na linha de comando é o modo como a aplicação deve ser executada. Caso a aplicação exija a utilização de outros argumentos, o candidato será reprovado automaticamente. Nesta etapa, todos os componentes já estão construídos. A figura 3 mostra um diagrama de classes em alto nível, que mostra a organização dos principais compo-nentes desenvolvidos neste artigo. Obviamente, a aplicação construída na atribuição real conterá outras classes, mas a arquitetura base da aplicação poderá se parecer com a arquitetura mostrada na figura 3.

Figura 3. Diagrama de classes dos principais componentes da aplicação.

A aplicação deve ser executada em três modos:-

mente, para que a comunicação possa ser estabelecida, o servidor deve estar em execução no momento que o cliente tentar se conectar. Este

Assim, o objeto de negócios é fornecido aos listeners que escutam os botões, para que estes executem suas funções corretamente. Para a JTa-

Page 11: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

72 www.mundoj.com.br

modo não requer que nenhum argumento seja passado na linha de co-mando, e pode ser executado da seguinte forma: java –jar runme.jar

-do, de forma a oferecer os serviços aos clientes que se conectarem. Caso o cliente seja um thin client, o servidor será responsável por implementar e oferecer os métodos de negócio a serem consumi-

ou será responsável por oferecer uma API parecida com a interface provida pela Sun, para que as regras de negócio sejam implemen-tadas no lado cliente (no caso do thick client). Este modo requer que o argumento “server” seja passado na linha de comando, e pode ser executado da seguinte forma: java –jar runme.jar server

utilizar o código do servidor. No caso deste exemplo, a camada de apre-sentação utiliza diretamente a classe DefaultExcursionServices, forneci-da quando a aplicação é iniciada neste modo. Este modo requer que o argumento “alone” seja passado na linha de comando, e pode ser execu-tado da seguinte forma: java –jar runme.jar alone

Levando em consideração o exemplo proposto por este artigo, os modos de execução da aplicação podem ser representados como mostra a figura 4.

Visão final do projeto

O mecanismo de bloqueio

Após empacotar o projeto, gerar os JavaDocs de todas as classes, gerar o user guide, criar o arquivo version.txt, gerar o arquivo choices.txt, listando todos os problemas encontrados e decisões tomadas, deve-se reunir todos

fundamental que o candidato atente para o formato e conteúdo do arquivo. O arquivo final deverá ter o seguinte formato:

modificações).

(respeitando a hierarquia de pacotes criada).

o sistema operacional utilizado para o desenvolvimento.

o O arquivo instructions.html, fornecido pela Sun. o Um subdiretório chamado javadoc, contendo a documentação

JavaDoc gerada pelo candidato. o Um arquivo chamado choices.txt, contendo as decisões de pro-

jeto tomadas pelo candidato. o Guia de usuário. Caso a documentação esteja dentro da aplica-

ção, este arquivo poderá ser omitido. Caso contrário, deverá se chamar userguide.txt ou poderão ser múltiplos arquivos .html, sendo que pá-gina inicial deverá se chamar userguide.html.

Cada aspecto desta certificação possui uma quantidade de pontos que pode ser atingida pelo candidato. O aspecto mais difícil e de maior quantidade de pontos é o mecanismo de bloqueio, que deve garantir que um cliente não sobrescreva inapropriadamente os dados fornecidos por outro cliente. Por exemplo, na atribuição real, um determinado registro deve ser atualizado com os dados de um consumidor caso o campo “customer” do registro esteja em branco. No caso de dois clientes tentarem reservar o mesmo registro ao mesmo tempo, somente um deve obter sucesso, e o outro deve receber uma mensagem informando que o registro não está mais disponível. Assim, o mecanismo de bloqueio deve garantir que quando um cliente bloqueia um registro, somente ele pode atualizar ou excluir esse registro, de forma a evitar a seguinte situação:

-

Com o mecanismo de bloqueio, um determinado registro fica protegido quan-do dois clientes tentam atualizá-lo ou excluí-lo ao mesmo tempo:

aguarda.-

queia e notifica os clientes em espera.

atualização, o desbloqueia e notifica eventuais clientes em espera.

Este mecanismo também pode ser implementado utilizando-se as classes de bloqueio da API de concorrência (localizadas em java.util.concurrent.locks). Como foi dito anteriormente, não é necessário disponibilizar na camada de negócios um método que exclua registros, porém o método de exclusão de registros fornecido na interface da Sun deve ser implementado e deve verificar se o cliente efetuando a exclusão de um registro é o mesmo que o bloqueou. Além disso, esse mecanismo também deve ser utilizado em modo “standalone”, mesmo que teoricamente só haja uma Thread em execução. O mecanismo também deve garantir que, se um determinado recurso já estiver bloqueado, as outras Threads não deverão consumir ciclos de CPU e deverão esperar até que o recurso seja liberado. Para esse requisito, pode-se chamar o método wait() dentro de uma instrução while. Na classe que implementa a interface fornecida pela Sun, pode-se definir uma estrutura do tipo Map<Long, Long> para controlar os registros bloqueados, em que a chave é o número do registro e o valor é um código que identifica o cliente que efetuou o bloqueio.

implementado.

Figura 4. Comunicação dos componentes em cada modo de execução da aplicação.

Page 12: Desmistificando a Certificação SCJD · Computação e está atualmente concluindo o curso de especialização em Engenharia de Software do ITA. Trabalha com ... O arquivo instructions.html

73

Considerações finais

package certification.database;

// imports omitidos...

public class Database implements LocalDB {

private Map<Long, Long> lockedRecords =

new HashMap<Long, Long>();

public synchronized void lock(long recordNumber)

throws ExcursionNotFoundException {

long clientId = Thread.currentThread().getId();

// Enquanto o registro a ser bloqueado estiver

// no Map lockedRecords, a Thread corrente

// deve esperar... a notificação para que as

// Threads que esperam por registros bloqueados

// acordem deve ser feita no método unlock(), e

// uma boa dica é que todas as Threads sejam notificadas.

// Após a notificação, deve-se verificar se o registro

// a ser bloqueado ainda existe, pois o método de

// exclusão de registros também exige que o

// mecanismo de bloqueio seja utilizado.

// Nos métodos de atualização e exclusão, deve-se

// verificar se o cliente requisitando a operação é o

// mesmo que bloqueou o registro, e somente o cliente

// que o bloqueou poderá desbloqueá-lo.

lockedRecords.put(recordNumber, clientId);

}

}

bloqueio.

Na atribuição real, deve-se bloquear um registro antes de reservá-lo. Dessa forma, no método de negócios que atualiza o registro, pode-se bloquear o registro e verificar se ele ainda está disponível para a atualização (ou se o campo “customer” ainda está em branco). Em caso afirmativo, efetua-se a atualização e desbloqueia-se o registro. Em caso negativo, pode-se lançar uma exceção negócios que pode estender a exceção ServicesException e se chamar RoomAlreadyBookedException. É fundamental que em ambos os casos o registro seja desbloqueado após a operação, caso contrário, as Threads aguardando por registros jamais serão notificadas e ocorrerá o deadlock. Dessa forma, a sequência de métodos a ser chamada é lock() – update() – unlock(). Importante: caso possa ocorrer deadlock na aplicação, o candidato será reprovado automaticamente.

No FAQ do fórum da certificação SCJD do JavaRanch pode-se encontrar um código feito pelo autor deste artigo que testa o mecanismo de bloqueio do projeto. Dessa forma, o candidato pode utilizá-lo e evitar a reprovação por exis-tência de possível ocorrência de deadlock na aplicação (que é a maior causa de reprovação nesta certificação).

package certification.business;

// imports omitidos...

public class DefaultExcursionServices implements ExcursionServices {

private LocalDB dbManager;

public DefaultExcursionServices(LocalDB dbManager) {

super();

this.dbManager = dbManager;

}

public void deleteExcursion(long recordNumber)

throws ServicesException {

try {

dbManager.lock(recordNumber);

dbManager.deleteExcursion(recordNumber);

dbManager.unlock(recordNumber);

} catch (ExcursionNotFoundException exception) {

// Caso ocorra, a ExcursionNotFoundException

// só poderá ter sido lançada pelo método

// lock()

throw new ServicesException(exception);

}

}

}

Este artigo mostra o que os candidatos podem esperar ao optar pela certifica-ção SCJD. O objetivo não é mostrar uma atribuição resolvida (o que seria contra as regras da própria certificação), mas sim como o candidato pode abordar o problema, modelar o sistema e resolver todas as etapas, através de um exem-plo fictício. Embora trabalhosa, essa certificação é um ótimo exercício no que diz respeito à modelagem de sistemas e resolução de problemas do mundo real, além de ser uma ótima oportunidade para se aprofundar em aspectos como programação multithreaded, Swing, design patterns e programação distribuída, e até mesmo aprender as convenções de código da Sun e como escrever corretamente comentários JavaDoc (onde algumas regras de escrita e utilização de tags devem ser seguidas). Uma boa dica é procurar resolver o que se pede da forma mais simples possível, e não fazer nada a mais. Além disso, o candidato deve estar atento a todos os “musts” do documento de instruções, que são as regras que devem obrigatoriamente ser seguidas. Como este artigo mostra, caso uma dessas regras não seja seguida, o candidato será reprova-do automaticamente. Em caso de dúvidas, o candidato pode procurar pelas respostas no fórum SCJD do JavaRanch, onde eu e outros desenvolvedores es-taremos prontos para ajudar. Esta certificação é um ótimo preparatório para a segunda fase da certificação SCEA, que requer que um sistema seja modelado e exige certo nível de experiência com modelagem de sistemas. Ao final da re-solução da atribuição, o candidato perceberá que evoluiu como desenvolvedor e que todo o esforço valeu a pena!

sun.com/docs/codeconv/>. Acesso em: 23 jan. 2010.

-

son-Wesley Professional, 2003.

-

sional, 2002.

Addison-Wesley Professional, 1994.

j2se/javadoc/writingdoccomments/>. Acesso em: 23 jan. 2010.

Developer-Certification-SCJD>. Acesso em: 23 jan. 2010.

2005.

Richards, Chris. POJOs In Action. Manning Publications, 2007.

Referências