Fast Lane Reader

Objectivo

Fornecer acesso rápido a coleções de dados em camadas inferiores, diminuindo a criação de objetos sem violar os contratos entre camadas.

Propósito

Fast Lane Reader (Leitor Via-Rápida), ou apenas , Fastlane, é um padrão de projeto que visa diminuir a criação de objetos durante a leitura de uma coleção de objetos diminuindo o caminho entre o consultor da lista e o repositorio dos dados reais. [1].

Num cenário de aplicação multi-camada os dados do objetos são lidos da fonte de dados (por exemplo, um banco de dados) e colocados em instancias, que são depois adicionadas em algum objeto de coleção (como, por exemplo, List ). Criar todos esses objetos e colocar numa coleção java seria demasiado oneroso quer em termos de memória, quer em termos de processamento. Estamos facilmente sujeitos a receber um OutOfMemoryError ou a que o processo demore tempo demais. Isto é muito comum quando precisamos criar um relatório em que precisamos iterar todos os itens selecionados mas eles são muitos.

A solução é simples de entender, mas não necessariamente de implementar. Em vez de criarmos todos os objetos de uma vez, colocar na coleção e descartar a fonte de dados ( normalmente um java.sql.ResultSet ) podemos encapsular a fonte de dados com a interface que queremos e construir o objeto de dados apenas quando necessário e um por vez.

A solução tipica é ter um objeto no padrão Iterator , em que temos um método para saber se ainda existem mais elementos e um métodos para obter o elemento seguinte.

A criação do objeto real que será retornado do iterador pode ser criado internamente, ou no caso geral podemos usar um objeto na padrão Factory. O objeto Factory está encarregue de pegar os dados brutos da fonte e os transformar em objetos. O objeto resultante não será guardado em lugar algum, diminuindo a quantidade de memória alocada durante a iteração.

Implementação

A seguir a estrutura de um objeto Fastlane que implementa a interface de um Iterator e lê um java.sql.ResultSet , que será a nossa fonte de exemplo. O tratamento de exceções é omitido para simplificar o entendimento.

public ResultSetFastlaneIterator<T> implements Iterator<T> {

 private ResultSet rs;
 private Factory<T> factory;

 public ResultSetFastlaneIterator ( ResultSet rs , Factory<T> factory ){
	this.rs = rs;
	this.factory = factory;
 }

 @Override
 public boolean hasNext (){
	boolean hasNext = rs.next ();
	if (!hasNext){ // não ha mais dados
		rs.close();
	}
	return hasNext;
 }

 @Override
 public <T> next (){
	return factory.createObject ( rs ) ;
 }

}

Utilizamos o objeto factory para construir o objeto a partir do registro atual no ResultSet . Isso poderia ser feito diretamente no código do método next(). Isso poderia ser feito diretamente no código do método next() caso saibamos o tipo especifico que será iterado.

Um detalhe importante é que apenas nos livramos da fonte de dados quando não houver mais dados para ler. O objeto de iteração implementado o FastLane pode continuar "vivo", mas liberamos os recursos que ele usa assim que possivel.

Comparemos os códigos utilizando o padrão Fastlane e sem utilizar o padrão. Este código lê objetos Customer que seria escrito num objeto que implemente o padrão DAO , por exemplo. O tratamento de exeções foi removido para simplificação.

// sem fastlane
public Iterator<Customer> getIteratorForQuery (){

 // obtém resultados do banco
 ResultSet rs = executeQuery () ;
 Factory<Customer> factory = new CustomerFactory() ;

 List<Customer> list = new LinkedList<Customer>() ;

 // itera TODOS os objetos
 while ( rs.next ()){
   list.add ( factory.createObject ( rs )) ;
 }

 // fecha e descarta o resultSet
  rs.close () ;

 return list.iterator () ;
}

// com fastlane
 public Iterator<Customer> getIteratorForQuery (){

 // obtém resultados do banco
 ResultSet rs = executeQuery () ;

 Factory<Customer> factory = new CustomerFactory() ;

 // não acontece nenhuma iteração.
 return new ResultSetFastlaneIterator ( rs,factory ) ;

 }

O código que usa o padrão é apenas um inicialização do objeto que implementa Fastlane, no caso o objeto ResultSetFastlaneIterator . Não ha iteração nem fabricação de nenhum objeto.O primeiro código cria e mantém 1+N objetos onde N é o numero de registros retornados na pesquisa.Na execução do iterador ele não cria mais nenhum objeto. Portanto, a cada iteração existem na memoria 1+N objetos. Contudo se existem N objetos no banco, o algoritmo itera 2N vezes, uma para colocar os objetos na lista e uma quanto o iterador for usado. O segundo métodos cria e mantém 3 objetos e durante a execução da iteração cria mais 1 objeto. Portanto, a cada iteração existem na memoria 4 objetos. Se existem N objetos no banco, este algoritmo os itera apenas uma vez.

Vamos fazer uma tabela das diferenças para ficar mais claro:

Objetos na memoria

Número de Iterações

sem Fastlane

1 + N

2N

com Fastlane

4

N

Usando Fastlane dimuimos os itens na memória e minimizamos o número de iterações sobre os dados.

A criação do objeto é delegada a um Factory. Eis um exemplo de como seria o código do método createObject

Discussão

O padrão FastLane Reader pretender acelerar a leitura de um conjunto de registros de forma eficiente. A forma de fazer isso é elidir da separação de camadas e acessar internamente à camada de dados de uma qualquer outra camada superior, mas sem violar o encapsulamento. O utilizador do objeto que implementa o padrão não tem conhecimento de que os dados estão vindo directamente da camada mais inferior. Para que este processo de comunicação "trans-camada" funcione, a camada onde o objeto é utilizado e a camada onde os dados são lidos têm que existir no mesmo nodo ( no caso, na mesma JVM). Caso contrário teremos que incluir logicas de Proxy remoto e com isso colocando barreiras na comunicação entre as camadas. A via de leitura não é mais livre, e portanto, não mais é rápida. O padrão FastLane fornece uma otimização de eficiência se utlizando do conceito de encapsulamento.

Em aplicações web é muito comun oferecer listagens para consulta pelo utilizador. Estas consultas são baseadas em objetos já que as tags JSP são normalmente baseadas em coleções. Contudo podemos "enganar" a camada

O exemplo de implementação acima mostra apenas o conceito por detrás do padrão.Na prática a implementação é mais complexa. Além do tratamento de exceções que foi omitido temos ainda que controlar situações de mau uso. Imagine por exemplo que usamos esse iterador num laço while e interrompemos esse laço com um break . O iterador não irá ler todos os objetos e portanto o hasNext nunca seria lido como falso e o ResultSet nunca seria fechado. Isto obviamente não é boa ideia. Poderíamos acrescentar um método close() ao contrato do nosso Fastlane, mas no caso, como o do exemplo, que estamos simulando um outro contrato pre-estabelecido, ninguém teria acesso a esse método. Truques como a sobrescrita do método finalize podem resolver, mas eles mesmos carecem de outros cuidados particulares [[2]] . Enfim, a implementção real de uma classe seguindo o padrão Fastlane não é simples e merece ser feita com cuidado.

Uma dica é usar interfaces especificas devem ser utilizandas em vez de interfaces da API padrão como Iterator. Desta forma métodos como close pode ser adicionados o que faria com que fossem usados nas camadas superiores. A implementação desses métodos pode ser vazia no caso simples de usar uma lista com todos os dados, ou não vazia no caso do fastlane.

Exemplos em APIs

Na API padrão temos o exemplo do uso do padrão Fastlane Reader o próprio ResultSet . Este objeto é implementado pelo driver JDBC e se comunica diretamente com o banco de dados, normalmente através de algum protocolo. Ao executar a pesquisa o driver é livre para não criar todos os objetos de todas as linhas, podendo usar um mecanismo de Fastlane para minimizar o uso de memoria.

Padrões associados

FastLane Reader pode ser entendido como uma especialização de Proxy em que o objeto constantemente comunica com um objeto em outra camada. Por outro lado, esse objeto não tem o mesmo contrato que o objeto Fastlane e por isso podemos entendê-lo como a especialização de Adapter . Por outro lado, ainda, podemos considerar que o trabalho de Fastlane é orquestrar o uso, e ciclo de vida, de outros objetos. Isso nos poderia levar a considerá-lo um Façade .

Um outro padrão relacionado ao FastLane Reader é o Flyweight . O objeto criado pelo FastLane Reader pode ser uma versão simplificada do objeto real. Desta forma, não só diminuímos o numero de objetos em memoria mas para cada um deles, diminuímos o numero de atributos preenchidos. No caso de Fastlane Reader ter o contrato de uma coleção ao invés de um iterador, podemos entender o próprio Fastlane Reader como uma especialização de Flyweight já que ao não carregar a coleção toda mantém o objeto de coleção leve ( como é o caso de ResultSet ).

Bibliografia

[1] Deepak Alur, John Crupi, Dan Malks Core J2EE Patterns: Best Practices and Design Strategies: Prentice Hall Professional (2003)

[2] Joshua Bloch Effective Java, 2nd Edition: Fonenix inc (2008)

Scroll to Top