quarta-feira, 5 de dezembro de 2007

Herança múltipla no Java

É um assunto já bem manjado, milhões de explicações pela internet, mas sempre aparece alguém dizendo que Java não tem herança multipla. Bem, é verdade, não existe, mas isso não quer dizer que não da para fazer. Segue um exemplo de como da para fazer herança múltipla com Java utilizando interfaces e agregação.

Considere uma interface que define um animal, não vou complicar enxendo de métodos, portanto vou tentar ser o mais simples possivel. Todo animal come portanto essa interface irá definir um método comer:

Animal.java

public interface Animal {
 void comer();
}

Vou criar agora duas novas interfaces, uma que define gatos e outra que define cachorros, sendo que ambas são animais também (claro).

Gato.java

public interface Gato extends Animal {
    void miar();
}
Cachorro.java
public interface Cachorro extends Animal {
    void latir();
}

Feito isso, implemento duas classes concretas: um gato (o gato siamês) e um cachorro (pastor alemão). Até ai tudo bem, nada de diferente ou de herança múltipla.

GatoSiames.java

public class GatoSiames implements Gato {
    public void miar() {
        System.out.println("miando");
    }
    
    public void comer() {
        System.out.println("gato comendo");
    }
}
PastorAlemao.java
public class PastorAlemao implements Cachorro {
    public void latir() {
        System.out.println("latindo");
    }
    
    public void comer() {
        System.out.println("cachorro comendo");
    }
}

Agora sim, começaremos as bizarrices, supondo que meu pastor alemão se engraçou com uma gata siamesa, no meu mundo isso é possível. No meu mundo também, a mãe é que da a característica principal do filho, ou seja, qualquer criatura que nasce de uma gata vai se parecer mais com um gato e qualquer coisa que nasce de uma cadela vai se parecer mais um cachorro. Logo, de acordo com as regras do meu mundo, quando o pastor alemão cruzou com a gata siamesa, nasceu uma criatura que se parece com um gato siamês, mas tem também características de um pastor alemão, ou seja, ela tem que herdar comportamento de ambas classes, mas as características de animal (comer), isso ela herda da mãe mesmo. Veja como usando interface e agregação é fácil modelar essa espécie bizarra:

GatoBizarro.java

public class GatoBizarro extends GatoSiames implements Cachorro {
    private PastorAlemao cachorro = new PastorAlemao();
    
    public void latir() {
        cachorro.latir();
    }
}

Perceba que se eu mandar esse bixo comer, ele vai comer feito um gato, na real ele pode ser considerado um gato pois sabe miar. Porem como é um filho de um cachorro, ele pode ser considerado como cachorro também pois sabe latir (e lati feito um pastor alemão), mas mesmo olhando para ele como um cachorro, comer que é o comportamento em comum entre todos animais, isso ele faz como um gato mesmo.

Agora vamos fazer o contrario, um gato siamês pulou a cerca com uma pastora alemã (como ele deu conta, ninguém sabe). Como eu já disse, quem manda na característica principal é a fêmea, logo nasceu algo parecido com cachorro. Eis a classe que representa essa criatura:

public class CachorroBizarro extends PastorAlemao implements Gato {
    private GatoSiames gato = new GatoSiames();
    
    public void miar() {
        gato.miar();
    }
}

Esse cara ai come feito cachorro, lati feito cachorro, mas sabe miar feito um gato siamês ;).

Viu como é fácil, usando de agregação e delegando ao objeto agregado é possível e fácil fazer herança múltipla com Java.

quinta-feira, 22 de novembro de 2007

Fazendo qualquer consulta ao banco de forma paginada.

Como eu imaginava, não tenho tempo para olhar muito isso e fazer novas postagens, mas juro que vou tentar melhorar hehe.

Já tem um tempo to devendo postar isso, é uma solução que fiz e uso para paginação no acesso a dados. Ela surgiu quando eu tinha que ler muitas tabelas e muitos registros de cada uma, o que gerava um OutOfMemory, então eu quis criar uma forma de que qualquer sql que eu executasse, ou até mesmo qualquer acesso a objetos (como por exemplo lendo de um XML, ou arquivo) que eu tivesse suporte a paginação de dados. Mas eu queria que isso fosse feito de forma transparente, ou seja, pudesse tratar esse retorno em for each do Java 5 por exemplo.

Não vou entrar em detalhes da implementação, mesmo porque não tem nada complexo, vou postar os códigos e um comentário simples apenas.

Esta é a interface de acesso que suporta paginação, todos meus objetos devem ser acessados a partir dessa interface, logo toda implementação que busca objetos (do banco, de arquivos, etc) implementa essa interface

public interface DataSet<T> extends Iterable<T> {

 List<T> list(int firstResult, int maxResults);

 List<T> listAll();

 int size();
}

O negócio é simples, apenas três métodos, um que exige suporte paginado, um que possibilita listar tudo e o ultimo que retorna o tamanho, além de estender Iterable (para eu que possa dar o for each). A próxima classe é justamente uma que implementa Iterable para poder ser usado em um DataSet:

public class DataSetIterator<T> implements Iterator<T> {

 private final DataSet<T> dataSet;
 
 private final int cacheSize;

 private List<T> cacheData;
 
 private int currentIt;
 
 private int totalReaded;

 public DataSetIterator(DataSet<T> dataSet, int cacheSize) {
  if (cacheSize <= 0 || dataSet == null) {
   throw new IllegalArgumentException();
  }
  this.dataSet = dataSet;
  this.cacheSize = cacheSize;
  this.currentIt = 0;
 }

 public boolean hasNext() {
  if (currentIt >= cacheSize || cacheData == null) {
   cacheData = dataSet.list(totalReaded, cacheSize);
   currentIt = 0;
  }
  return cacheData.size() > currentIt;
 }

 public T next() {
  if (hasNext()) {
   totalReaded++;
   return cacheData.get(currentIt++);
  } else {
   throw new NoSuchElementException();
  }
 }

 public void remove() {
  throw new UnsupportedOperationException();
 }
}

A próxima classe é só uma abstração de DataSet que implementa os métodos exigidos pela interface Iterable, no caso usando o Iterator criado anteriormente

public abstract class AbstractDataSet<T> implements DataSet<T> {
 public Iterator<T> iterator() {
  return new DataSetIterator<T>(this, 50);
 }
}

Agora vou criar duas classes que serão úteis para meus DataSet's que acessam o banco de dados. A primeira classe é justamente a implementação de um DataSet mas que lê dados uma List do Java, isso vai ser útil para caso seja utilizado o método listAll de algum DataSet que acessa o banco, nas próximas consultas ao mesmo DataSet não será mais lido do banco (pois já leu tudo) e sim de uma lista em memória (DataSet de lista).

public class ListDataSet<T> extends AbstractDataSet<T> {

 private final List<T> list;

 public ListDataSet(List<T> list) {
  if (list == null) {
   throw new IllegalArgumentException();
  }
  this.list = list;
 }

 public List<T> list(int firstResult, int maxResults) {
  if (firstResult > list.size()) {
   return new ArrayList<T>();
  }
  if (firstResult < 0 || maxResults <= 0) {
   return list;
  }
  int toIndex = Math.min(firstResult + maxResults, list.size());
  return list.subList(firstResult, toIndex);
 }

 public int size() {
  return list.size();
 }

 public List<T> listAll() {
  return list;
 }
}

A segunda é uma interface, sua função é como se fosse um Factory. Vai servir para obter o Criteria quando fizer um DataSet que usa Criteria do Hibernate ou obter a Query quando fizer um DataSet que usa Query do JPA.

public interface Provider<T> {
 
 T get();

}

Agora sim, finalmente os DataSet's que fazem acesso ao banco de dados. O primeiro serve para fazer consultas usando ejbql do JPA. Ele recebe dois providers (fabricas, como você achar melhor), um para obter a Query da consulta em si e outro para obter a Query que busca a quantidade de registros (um count da consulta não paginada).

public class JpaQueryDataSet<T> extends AbstractDataSet<T> {

 private Integer size;

 private ListDataSet<T> listDs;

 private final Provider<Query> queryProvider;

 private final Provider<Query> countQueryProvider;

 public JpaQueryDataSet(Provider<Query> queryProvider,
   Provider<Query> countQueryProvider) {
  if (queryProvider == null || countQueryProvider == null) {
   throw new IllegalArgumentException();
  }
  this.size = null;
  this.listDs = null;
  this.queryProvider = queryProvider;
  this.countQueryProvider = countQueryProvider;
 }

 @SuppressWarnings("unchecked")
 public List<T> list(int firstResult, int maxResults) {
  if (listDs != null) {
   return listDs.list(firstResult, maxResults);
  } else {
   final Query query = queryProvider.get();
   if (firstResult < 0 || maxResults <= 0) {
    final List<T> list = query.getResultList();
    size = list.size();
    listDs = new ListDataSet<T>(list);
    return list;
   } else {
    query.setFirstResult(firstResult);
    query.setMaxResults(maxResults);
    final List<T> list = query.getResultList();
    return list;
   }
  }
 }

 public int size() {
  if (size == null) {
   final Query query = countQueryProvider.get();
   size = ((Long) query.getSingleResult()).intValue();
  }
  return size;
 }

 public List<T> listAll() {
  return list(-1, -1);
 }
}

Esse segundo DataSet serve para fazer consultas usando criteria do Hibernate. Ele recebe um provider justamente para obter o Criteria:

public class HibernateCriteriaDataSet<T> extends AbstractDataSet<T> {

 private Integer size;

 private ListDataSet<T> listDs;

 private final Provider<Criteria> criteriaProvider;

 public HibernateCriteriaDataSet(Provider<Criteria> criteriaProvider) {
  if (criteriaProvider == null) {
   throw new IllegalArgumentException();
  }
  this.criteriaProvider = criteriaProvider;
 }

 @SuppressWarnings("unchecked")
 public List<T> list(int firstResult, int maxResults) {
  if (listDs != null) {
   return listDs.list(firstResult, maxResults);
  } else {
   final Criteria crit = criteriaProvider.get();
   if (firstResult < 0 || maxResults <= 0) {
    final List<T> list = crit.list();
    size = list.size();
    listDs = new ListDataSet<T>(list);
    return list;
   } else {
    crit.setMaxResults(maxResults);
    crit.setFirstResult(firstResult);
    return crit.list();
   }
  }
 }

 public List<T> listAll() {
  return list(-1, -1);
 }

 public int size() {
  if (size == null) {
   final Criteria crit = criteriaProvider.get();
   crit.setProjection(Projections.rowCount());
   size = (Integer) crit.uniqueResult();
  }
  return size;
 }
}

Claro que nem todas consultas você vai conseguir fazer usando isso, então encare mais como um utilitário. Segundo é, para esses DataSet que acessam banco de dados, óbvio que precisa ser executado em um contexto transacional.

Para comprovar que funciona, criei uma tabela cargo com id e nome, e inseri alguns registros. Buscando todos registros através de um DataSet, fazendo um for each mandando meu iterator ler paginado de 5 em 5 (configuravel dentro de AbstractDataSet) e exibindo por primeiro o número de registros da consulta. Perceba que a cada 5 registros lidos um novo sql é executado para buscar os próximos (claro que 5 é um número muito pequeno para paginar, aqui estou usando só como exemplo, edite AbstractDataSet de acordo com suas necessidades ou até mesmo deixe isso configuravel nas sub classes):

DataSet<Cargo> ds = Cargo.findAll();
  System.out.println(ds.size());
  for (Cargo c : ds) {
   System.out.println(c.getName());
  }
Console:
Hibernate: select count(*) as col_0_0_ from cargo cargo0_
332
Hibernate: select cargo0_.cd_cargo as cd1_0_, cargo0_.nm_cargo as nm2_0_ from cargo cargo0_ limit ?
Account Manager
Administrador(a)
Administrador(a) Banco de Dados
Administrador(a) Empresas
Administrador(a) Materiais
Hibernate: select cargo0_.cd_cargo as cd1_0_, cargo0_.nm_cargo as nm2_0_ from cargo cargo0_ limit ?, ?
Administrador(a) Rede
Advogado(a)
Agente Administrativo
Agente Comercial
Agente Compras
Hibernate: select cargo0_.cd_cargo as cd1_0_, cargo0_.nm_cargo as nm2_0_ from cargo cargo0_ limit ?, ?
Agente Financeiro
Agente Negócios
...

Segue o que seria a implementação do método findAll. Esse primeiro usando o HibernateCriteriaDataSet:

final Session session = //obtem o Session, seja via Spring, ou como vc quiser
   final Provider<Criteria> crit = new Provider<Criteria>() {
    public Criteria get() {
     return session.createCriteria(Cargo.class);
    }
   };
   return new HibernateCriteriaDataSet<Cargo>(crit);
E esse segundo usando JpaQueryDataSet:
final EntityManager entityManager =  //obtem o EntityManager, seja via Spring, ou como vc quiser
   final String sql = "select e from " + Cargo.class.getName() + " e";
   final String countSql = "select count(*) from "
     + Cargo.class.getName() + " e";
   final Provider<Query> queryProvider = new Provider<Query>() {
    public Query get() {
     return entityManager.createQuery(sql);
    }
   };
   final Provider<Query> countQueryProvider = new Provider<Query>() {
    public Query get() {
     return entityManager.createQuery(countSql);
    }
   };
   return new JpaQueryDataSet<Cargo>(queryProvider, countQueryProvider);

Bem, eu não postei, mas já fiz implementações dessas que acessam objetos em xml e até mesmo um DataSet que lê paginado de outros DataSet's (por exemplo fazer um for each que lê de um banco de dados e de um arquivo xml de forma transparente e paginada). Pode ser que exista soluções muito melhores, mas na época não tinha achado nada que fizesse o que eu queria, por isso criei esse jeito, o que vem me quebrando um bom galho ultimamente.

sábado, 1 de setembro de 2007

Maria vai com as outras... ;)

Então, resolvi fazer um blog, nunca fiz nenhum, e provavelmente já pensei que nunca teria hehe mas como sou Maria vai com as outras, já que tá na moda! ahah brincadeira, acho interessante, últimamente boa parte dos meus estudos estão saindo de postagem em blog's, portanto é uma boa ferramenta para compartilhar conhecimento e interesses, e até mesmo servir de base de dados. Não pretendo contar detalhes da minha vida, nem ficar postando "o que eu fiz hoje" (salvo no verão que vou ter que dizer que fui para praia em Floripa, que por sinal fica logo ali né, deixando assim as pessoas com água na boca), quero usar esse blog mais para postagem de assuntos relacionados a minha profissão, em específico Java que hoje é meu ganha pão e o que mais estudo, mas também informática em geral, e porque não, investimentos e economia que é meu grande objetivo a longo prazo (afinal, trabalhar 8h/d nínguem merece, isso não é vida). É isso ai, acho que não sou daqueles que vai ficar atualizando isso direto, mesmo porque não tenho tempo, já que trabalho, cursos de idioma, dança e o futebol me tomam um bocado de tempo, mas tentarei fazer desta ferramenta algo util para todos. valeu!