quinta-feira, 30 de agosto de 2007

Fábrica de DAOs com Reflection

Olá a todos,
Sabemos que uma boa prática na modelagem de arquiteturas de software é a serapação das responsabilidades em camadas bem definidas. Uma destas camadas, talvez a que esteja mais relacionada com os recursos da infra-estrutura, é a de Persistencia dos dados. Para esta camada, é comum utilizar-se o pattern Data Access Object (DAO), que tem o objetivo de encapsular estes acessos aos recursos de armazenamento de dados (normalmente bancos de dados relacionais).
Além da separação de camadas, ouve-se muito falar que é importante manter um fraco acoplamento entre elas, tornando assim, mais independentes para futuras manutenções. Mas como garantir este fraco acoplamento? Segue um exemplo de utilização de uma DAO na camada de negócios:

ClienteDao dao = new ClienteDao(HibernateUtil.getSession());
dao.adiciona(cliente);

Implementando desta forma, caso seja necessário refatorar a classe de acesso a dados de Clientes, ou mesmo criar uma nova versão desta classe que não utilizasse hibernate, mas sim acesso direto via JDBC, o que teria que ser feito? Teríamos que criar uma nova classe, por exemplo: ClienteDaoJDBC e implementar todos os metodos novamente. Até aí tudo bem, nada problemático. Mas o problema está na utilização da classe, imagine que quem for cliente da classe ClienteDao terá que mudar o tipo da variável/atriuto para ClienteDaoJDBC. Aí que está o Alto acoplamento.
Para resolver isso é interessante criar interfaces para as DAOs, e então as classes ClienteDao e ClienteDaoJDBC implementariam estas interfaces. Quem fosse utilizar as DAOs usariam variável/atributo do tipo da interface: IFClienteDao por exemplo. Com isso já resolvemos o problema de ter que alterar o tipo da variável/atributo, mas ainda temos o alto acoplamento representado pela instrução new, conforme o código abaixo:

IFClienteDao dao = new ClienteDaoJDBC();

Para resolvermos o problema do alto acoplamento, devemos então criar uma Fábrica de Daos. Uma classe que fosse responsável por instanciar nossas DAOs e tirar esta responsabilidade do cliente das classes DAOs, assim o dia que for necessário alterar a DAO a ser usada, seria só alterar nesta Fábrica. Este conceito de Fábrica segue o pattern Factory definido pelo GoF.
Uma fábrica de Daos poderia ser uma singleton, pois não parece ser necessário ter mais de uma instancia desta classe, então o código ficaria:

public class DaoFactory {
// controle da singleton
private static DaoFactory factory = new DaoFactory();

// construtor private para não permitir instanciar a factory diretamente com new.
private DaoFactory() {}

// através deste método é que se obtem a fábrica.
// excepcionalmente este método recebe um atributo session pois este pode mudar
// durante o tempo de vida da aplicação se assim o hibernate desejar :D
public static DaoFactory getInstance(Session session) {
factory.setSession(session);
return factory;
}

public IFClienteDao getClienteDao() {
return new ClienteDaoJDBC();
}
}


Com esta fábrica é possível obter DAOs sem saber o que é preciso disponibilizar à ela, mais uma vez o papel de encapsulamento ajudando.
Mas ainda tem-se um pequeno problema, conforme o sistema for crescendo, muitas daos são criadas e muitos getXXXDao() vão sendo adicionados à fabrica. Quando ocorre estes métodos repetitivos, já é possível imaginar que poderia-se criar um getDao genérico que retornasse a Dao respectiva de uma classe persistente, por exemplo: "Quero que retorne a DAO referente a persistencia dos dados da classe Cliente", então a fábrica retornaria a ClienteDaoJDBC por exemplo. Para isso pode ser utilizado Java Reflection conforme o código a seguir:

private static DaoFactory factory = new DaoFactory();
private Session session;
private Map daoMap = new HashMap();

public Dao getDao(Class persistentClass) {
if (daoMap.containsKey(persistentClass)) {
return daoMap.get(persistentClass);
}
else {
if (persistentClass.isAnnotationPresent(DaoClass.class)) {
DaoClass daoClassInfo = (DaoClass)persistentClass.getAnnotation(DaoClass.class);
Dao dao = null;
try {
dao = daoClassInfo.value().getConstructor(Session.class, Class.class).newInstance(this.session, persistentClass);

if (daoClassInfo.singleton()) {
daoMap.put(persistentClass, dao);
}

return dao;
}
catch (Exception e) {
throw new DaoFactoryException("A classe Dao não pode ser instanciada. Erro: " + e.getMessage());
}
}
else {
throw new DaoFactoryException("Anotação @DaoClass ausente");
}
}
}
Não adicionei comentários neste código pois está bem legível. Espero que tenha sido útil.

[]'s

Nenhum comentário: