Carregando...

segunda-feira, 21 de dezembro de 2009

Best Pratices - Static factory

segunda-feira, 21 de dezembro de 2009
Ao iniciarmos no conceito da orientação a objetos, muitas vezes não entendemos plenamente como usar a orientação a objetos, ninguém está isento disso, e o maior problema é que muitas vezes as pessoas se fecham a acham que sabem tudo sobre orientação a objetos, ou que apenas por usar uma classe já está usando orientação a objetos, estes são erros comuns, por exemplo veja os programadores que utilizam o Delphi, embora usem objetos, a programação parece ser mais estruturada e a lógica fica dispersa nas ações, o código fica de difícil manutenção.

Geralmente quando ocorre este problema o mais fácil as vezes é reescrever todo o sistema, isto mesmo se você já teve este pensamento, você possivelmente está fazendo alguma coisa errada.

Martin Flower se refere a estes problemas como mal cheiros no código, bem agora iremos abordar exatamente um destes problemas.

A forma mais natural de obtermos uma instância de um objeto é através do uso de um construtor, usado em java da seguinte forma:
Relatorio relContas = new Relatorio();
ao executarmos este código obteremos um objeto chamado relatório, mas uma abordagem como está é pouco flexível, considere que você precisa configurar dados do relatório para que o mesmo seja usado, uma forma de fazer isto é por oferecer a os dados necessários para a classe relatório funcionar, como parametros no construtor, veja o exemplo:
Relatorio relContas = new Relatorio("Relatorio de contas dos clientes",new Date());
repare que agora o nosso objeto relatório é gerado usando parâmetros no construtor, qual o problema desta abordagem? apenas analisando este trecho de código você não seria capaz de dizer o que os parâmetros que estão sendo passados representam, repare que você neste momento teria de fazer uma consulta ao javadoc da classe ou mesmo acessá-la para verificar o que ela está querendo dizer.

Outro problema que temos é que não conseguimos expandir as funcionalidades da classe, repare que se requisermos fazer cache o objeto relatório isto não será possível.

Usarmos uma abordagem como está com certeza (uso do operador new), torna o sistema menos flexível e conseqüentemente de mais difícil manutenção.

Podemos resolver este problema de duas maneiras, usarmos StaticFactorys.

Uma static factory permite você configurar melhor a sua classe e expor também o que você deseja fazer.

Veja o exemplo usando o conceito de static factory:
Relatorio relContas = Relatorio.comTitulo("Relatorio de contas do Cliente");
repare que agora o código parece fazer mais sentido, ao ver esta linha fica claro que você está dizendo qual é o título do relatório.

a classe fica assim:
public class Relatorio{
    ...

    private Relatorio(){}
    
    public static Relatorio comTitulo(String titulo){
        Relatorio rel = new Relatorio();
        rel.setTitulo(titulo);
        return rel;
    }

    ...
}
O uso de static factory method possui as vantagens que se você deseja tornar o objeto um Sigleton, basta modificar a fábrica estática, se você deseja fazer cache de objetos num pool para compartilhar entre as chamadas, uma abordagem como está com toda certeza permite esta flexibilidade.

Posteriormente irei abordar o uso do padrão Builder para a construção de objetos.


Nenhum comentário :

quarta-feira, 9 de dezembro de 2009

Repositorio, e especificações como implementar - Parte 2

quarta-feira, 9 de dezembro de 2009
Em meu post anterior citei como fazer a implementação flexível do padrão Repositório e Especificação, citado por Eric Evans juntamente com o Hibernate, realmente as pessoas tem muita confusão de como implementar, uma coisa sempre e certa a sempre 1001 maneiras de implementar algo, e como Eric Evans, em seus exemplos, foi dado do SQL direto a implementação da Especificação e do Repositório, mas nós podemos usar o Hibernate que facilita muito a vida, e ele disse também sobre isto, que muitas vezes o sistema de mapeamento objeto relacional pode nos ajudar.

Mas vamos ao que interessa, no post anterior falei a respeito de como implementar a Especificação e vimos que a mesma serve para manter as regras de negócio da aplicação toda sobre um único lugar, assim o sistema se torna de mais fácil manutenção, e podemos ganhar muito com isto, nem sempre e fácil fazer a implementação destes padrões, visto a grande confusão e insistência em fazer as coisas usando as receitas de bolo da programação.

Algo que com certeza podemos mais cedo ou mais tarde fazer nos nossos sistemas é a junção de filtros para obter um resultado, vimos que a Especificação pode nos ajudar a abstrair do cliente toda a complexidade do filtro da consulta e manter as coisas simples, mas vamos a um exemplo real no sistema.

Digamos que o cliente diga o seguinte a você que está desenvolvendo o sistema.

"O relatório de clientes está bom mas, gostaríamos que na tela que faz a configuração do relatório seja incluído outros filtros onde gostaríamos de filtrar os clientes do relatório que possuem não contatamos a mais de 20 dias, visto que estes clientes devem ter uma atenção especial, assim como já funciona na tela do cadastro, e gostaria além disso também que fosse exibido os que tiveram uma reclamação do nosso serviço no histórico."

Repare que neste dialogo o cliente solicitou que você casa-se ou junta-se filtros diferentes, um filtro já existe, sendo que já está funcionando na tela de cadastro, a Especificação que já está implementada e a de clientes sem contato a mais de 20 dias, agora ele te Especificou algo novo, o cliente que possui uma ou mais reclamações. Vejamos então o que temos e o que deveríamos desenvolver:

public class EspecificacaoClientesComReclamacao implements Especificacao {

    private Integer numReclamacoes;
 
    public EspecificacaoClienteComReclamacao(Integer numReclamacoes){
        this.numReclamacoes = numReclamacoes;
    }
 
    public Criterion configurarCriteria(Criteria criteria,Class alvo){
        if (alvo != Cliente.class) return null;
        return Restrictions.gt("numReclamacoes", numReclamacoes );
    }
}
E a outra especificacao:

public class EspecificacaoClientesSemContato implements Especificacao {
    private Integer dias;
 
    public EspecificacaoClienteSemContato(Integer dias){
        this.dias = dias;
    }
 
    public Criterion configurarCriteria(Criteria criteria,Class alvo){
        if (alvo != Cliente.class) return null;
        Date dataAtual = new Date();  
        Calendar calendarData = Calendar.getInstance();  
        calendarData.setTime(dataAtual);  
        calendarData.add(Calendar.DATE,dias);  
        Date dataInicial = calendarData.getTime();  
  
        return Restrictions.gt("ultimoContato", dataInicial );
    }
}

Repare que no dialogo o cliente queria executar estas duas especificações para obter a lista?

Como fazer isto então? Bem conforme Evans comentou podemos pegar emprestado o conceito de operadores lógicos, o que são estes operadores lógicos? Seria o seguinte:

AND, OR, NOT

Veja como ficaria para usarmos os operadores lógicos, e assim agrupar as duas Especificações:
Especificacao clientesSemContato = new EspecificacaoClientesSemContato();

Especificacao clientesComReclamacao = new EspecificacaoClientesComReclamacao();

Especificacao especificacoes = clientesSemContato.and(clientesComRelamacao);
Repare que na ultima linha juntamos as duas especificações e agora poderíamos passar para o repositório as especificações agrupadas assim.
RepositorioCliente clientes = new RepositorioCliente();
clientes.recuperarPorEspecificacao(especificacoes);
Desta forma estaríamos passando não uma mas sim duas especificações que poderiam ser executadas.

E digamos que queríamos os clientes sem contato OU os clientes com reclamação? Poderíamos fazer assim:
Especificacao especificacoes = clientesSemContato.or(clientesComRelamacao);
Repare agora o uso do operador or? O mesmo se aplica ao NOT onde queremos os clientes sem contato que não tiveram reclamação:
Especificacao especificações = clientesSemContato.not(clientesComRelamacao);

Repararam no poder deste simples sistema? O cliente tem total liberdade para especificar que resultado deseja sem se preocupar em como ele será obtido, sendo que como ele é obtido fica a cargo do repositório.


Pois bem como implementar isto em Java e usando o hibernate?

A Especificação que era uma interface agora irá usar o padrão Template Method, ela passará a ser conforme mostrado abaixo:

public abstract class Especificacao {
    public Especificacao and(Especificacao... especificacoes) {
        List<especificacao> especs = new ArrayList<especificacao>();
        for (Especificacao e : especificacoes)
            especs.add(e);
        especs.add(this);

        return new EspecificacaoAnd(especs.toArray(new Especificacao[especs.size()]));
    }

    public Especificacao not() {
        return new EspecificacaoNot(this);
    }

    public Especificacao or(Especificacao... especificacoes) {
        List<especificacao> especs = new ArrayList<especificacao>();
        for (Especificacao e : especificacoes)
            especs.add(e);
        especs.add(this);

        return new EspecificacaoOr(especs.toArray(new Especificacao[especs.size()]));
    }

    @SuppressWarnings("unchecked")
    public abstract Criterion configurarRestricao(Criteria criteria, Class classe) throws DomainException;

    public abstract boolean satisfazEspecificacao(Object o) throws DomainException;
    }
}
E os objetos que fazem uso do operador AND,OR,NOT são implementados no exemplo abaixo:

Especificação Or:

class EspecificacaoOr extends Especificacao {

    private Especificacao[] especificacoes;

    public EspecificacaoOr(Especificacao... especificacoes) {
        this.especificacoes = especificacoes;
    }

    @SuppressWarnings("unchecked")
    public Criterion configurarRestricao(Criteria criteria, Class classe) throws DomainException {
        Disjunction or = Restrictions.disjunction();

        for (Especificacao espec : especificacoes) {
            if (espec == null) continue;
            Criterion ret = espec.configurarRestricao(criteria, classe);
            if (ret != null)
                or.add(ret);
        }

        return or;
    }

    public boolean satisfazEspecificacao(Object o) throws DomainException {
        for (Especificacao espec : especificacoes) {
            boolean retorno = espec.satisfazEspecificacao(o);
            if (retorno)
                return true;
        }
        return false;
    }
}
Especificação AND:

class EspecificacaoAnd extends Especificacao  {
    private Especificacao[] especificacoes;
    public EspecificacaoAnd(Especificacao... especificacoes) {
    this.especificacoes = especificacoes;
    }
    @SuppressWarnings("unchecked")
    @Override
    public Criterion configurarRestricao(Criteria criteria,Class classe) throws DomainException {
        Conjunction and = Restrictions.conjunction();
        List<criterion> retorno = new ArrayList<criterion>();
        for (Especificacao espec : especificacoes) {
            if (espec == null) continue;
            Criterion ret = espec.configurarRestricao(criteria,classe);
            if (ret != null){
                retorno.add(ret);
                and.add(ret);
            }
        }

        if (retorno.size() == 0) return null;
  
        return and;
    }

    @Override
    public boolean satisfazEspecificacao(Object o) throws DomainException{
        for (Especificacao espec : especificacoes) {
            Boolean retorno = espec.satisfazEspecificacao(o);
            if (!retorno)
                return false;
        }
        return true;
    }
}
Especificação NOT:

class EspecificacaoNot extends Especificacao {

    private Especificacao espec;

    public EspecificacaoNot(Especificacao especificacao) {
        this.espec = especificacao;
    }

    @SuppressWarnings("unchecked")
    public Criterion configurarRestricao(Criteria criteria,Class classe) throws DomainException {
        if (espec == null) return null;
        Criterion ret = espec.configurarRestricao(criteria,classe);
        if (ret != null)
            return Restrictions.not(ret);

        return null;
    }

    public boolean satisfazEspecificacao(Object o) throws DomainException {
        return !espec.satisfazEspecificacao(o);
    }
}
Com estas especificações agora podemos usar o sistema conforme exemplificado e passar para o repositório agora não mais uma especificação e sim varias.

Conforme podemos ver alisto abaixo o real benefício desta implementação:

1. Classes mais leves: O repositório passa a ter uma interface simples e não recheada de métodos.
2. As regras da aplicação ficam em apenas um lugar.
3. As especificações podem ser combinadas e assim filtros mais complexos, com uso dos operadores lógicos.
4. Maior clareza, e encapsulamento da regra.

Em outro Post mostrarei como utilizar as especificações para executar outras coisas como recuperar um conjunto de registros paginado, Ordenado, usando especificações e como fazer isto na prática, postarei um código de exemplo.


Nenhum comentário :

terça-feira, 1 de dezembro de 2009

Repositorio, e especificações como implementar

terça-feira, 1 de dezembro de 2009
Algo que inconfundivelmente geralmente toda aplicação precisa ter, é o acesso a dados em um banco de dados. Antigamente as aplicações acessavam os dados gerando consultas diretamente da interface do usuário, esta abordagem acoplava a consulta com a interface visual, o que inevitavelmente levava a duplicar consultas em mais de uma área, como para tirar um relatório e para uma consulta ao sistema.

Mas ai as coisas foram evoluindo, a melhor coisa seria então separar a camada que acessava os dados da camada visual ou qualquer outra camada, para isto foi introduzido um conjunto de classes que faziam a separação dos dados sendo que estas classes agora eram responsáveis por fazer de fato o acesso aos dados, converter em objetos e repassar para o seu cliente (quem invocou o método) uma lista de objetos que representavam assim uma consulta ao banco de dados.

Quando uma solução se repete demais, podemos dizer que ela é um padrão, portanto assim surgiu o DAO ou Pattern DAO.

Este padrão visava além de desacoplar do cliente que necessita de dados do banco, tinha também o propósito de permitir o suporte a diversos bancos de dados, podendo a fonte dos dados vir até mesmo de um arquivo de texto, um WebService, XML, ou outra fonte qualquer, mas isto trouxe uma complexidade que ainda precisava ser atacada, a questão dos bancos de dados, diversos bancos de dados também era um problema visto que geralmente aquele o aquele outro banco tinha as suas peculiaridades, embora o uso do pattern DAO tenha desacoplado e permitido diversas implementações de acesso a fontes de dados diferentes, ainda era custoso ter que desenhar para bancos diferentes, além de ser extremamente repetitivo a aplicação do padrão, para permitir o acesso a uma fonte diferente de dados.

Como forma de unificar isto surgiu em cena um framework muito conhecido, o Hibernate, que agora fazia com que o acesso aos dados fosse facilitado, os DAOs agora podiam ser feitos de uma forma mais fácil.

Ainda em outro campo o Hibernate influenciou fortemente a criação da especificação JPA, que veio para substituir as Entity Beans CMP da especificação EJB 2, esta foi mais uma evolução onde agora mapeávamos diretamente através de anotações uma entidade e assim fazer o acesso aos dados.

Ao analisar, hoje vemos que progredimos muito com respeito ao acesso de dados, mas vemos que muitos tem implementado o padrão DAO como uma receita de bolo, muitas vezes sem entender o que ele faz. Muitos acabam implementando ele com a seguinte interface:
public interface DAO<t> {
    int count();
    
    List<t> findAll(int start, int max);

    Lis<t> findAll();

    T findById(Long id);
}
E quando precisão implementar uma consulta mais especifica acabam fazendo assim
public class DAOCliente {
    private List findClientesPorNome(String nome){
    .....
    }
} 
E se agora precisar filtrar por CPF e CNPJ ?

A resposta mais obvia era, implemento mais um método
private List<cliente> findClientesPorCNPJ(String CNPJ){
  .....
}

private List<cliente> findClientesPorCPF(String CPF){
  .....
}
Se pararmos para pensar por um momento veremos que isto faz com que a interface fique inchada, fazendo assim que o sistema fique muito rígido, exemplo de rigidez? Digamos que você precise contar os clientes que começam com um nome, outro método seria então implementado.

Pois bem percebendo isto muitos desenvolvedores, assim como eu vinham buscando soluções para tornar o sistema, mas flexível para consultas.

Ao ler o livro de Eric Evans Domain Driven Design, e ver diversos post do Shoes e muitos outros, pudemos ver uma abordagem melhor para acesso aos dados. Do que uma interface inflada.

Bem vamos explicar a que conclusão chegamos, um dos primeiros pontos que vimos foi que o cliente era responsável por dizer o que queria, isto também ocorria antes, visto que o cliente tinha de dizer que método do DAO era necessário invocar, agora o que fizemos foi diferente, ao invés de implementar rigidamente a consulta sobre o DAO que segundo Shoes, nos passaremos a chamá-lo a partir daqui de Repositório foi encapsular o filtro da consulta sob uma especificação, o que é uma especificação? Nada mais é do que dizer como você quer as coisas, sendo que ela serve tanto para acesso a dados de um banco mais também para validação, vamos a um exemplo para que fique mais claro:

- Primeiro vamos ver um exemplo de uma especificação para validação:

Digamos que você está montando um sistema de estoque e que agora você necessita comprar mais itens dos produtos que estão acabando, geralmente você iria fazer um IF sendo que os produtos que tiverem uma quantidade X seria adicionado:

for ( Produto produto : produtosEstoque) {
    if (produto.getQuantidadeEmEstoque()<15){
        listaDeProdutosAComprar.add(produto);
    }
}
Com certeza este código funciona e você obterá uma lista de produtos que estão com menos de 15 unidades no estoque. Mas um problema é que a lógica de produtos como esta pode ficar dispersa em vários locais da aplicação. Sendo assim você deveria encapsular a lógica de verificação, fazendo uma especificação assim:

Você tem a seguinte interface:

public interface Especificacao {
    boolean satisfaz(Object o);
}

public class EspecificacaoDeProdutosAcabando implements Especificacao {

    private int quantidadeMinima;

    public EspecificacaoDeProdutosAcabando(int quantidadeMinima){
        this.quantidadeMinima = quantidadeMinima;
    }

    public boolean satisfaz(Object o){
 
        // aqui vai a lógica para verificar se o produto não está acabando
 
        if (!(o instanceof Produto)){
            return false;
        }

        return ((Produto) o).getQuantidadeEmEstoque()<quantidadeMinima;
    }
}
Para usar agora você faz assim:

Especificacao produtoAcabando = new EspecificacaoDeProdutosAcabando(15);

for ( Produto produto : produtosEstoque) {
    if (produtoAcabando.satisfaz(produto)){
        listaDeProdutosAComprar.add(produto);
    }
}
Repare que ao fazer assim você encapsulou a lógica de verificação de um produto quanto a se ele deve ser comprado ou não, se agora para modificar a lógica se um produto está acabando, você simplesmente altera em um lugar.

Este sistema tem grande poder para objetos agora também pode ser usado para filtrar registros no banco de dados.

Para fazer isto eu uso para as consultas a api criteria do Hibernate.

A interface da especificação precisa ser modificada agora para o seguinte:

public interface Especificacao {

    boolean satisfaz(Object o);

    Criterion configurarCriteria(Criteria criteria,Object o);

}
O papel do método Criterion configurarCriteria(Criteria criteria,Object o) é criar um criterion do tipo restriction para que possa ser adicionado a criteria e assim efetuar o filtro no sistema.

Veja uma real implementação onde é criado a especificação de login valido para ser usado por um repositório.

Em uma aplicação apenas os usuários com senha e login validos e que estão ativos devem acessar ao sistema. Portanto teremos a seguinte especificação:

public class EspecificacaoLoginValido implements Especificacao {

    private String login;
    private String senha;
    
    public EspecificacaoLoginValido(String login,String senha){
        this.login = login;
        this.senha = senha;
    }
 
    public boolean satisfaz(Object o) {
        if (!(o instanceof Usuario) || o ==null){
            return false;
        }

        Usuario u = (Usuario) o;

        return login.equals(u.getLogin()) 
        && senha.equals(u.getSenha()) 
        && u.isAtivo();

    }    

    public Criterion configurarCriteria(Criteria criteria,Object o){

        if (!(o instanceof Usuario) || o ==null){
            return null;
        }
        
        Conjunction and = Restrictions.conjunction();
        and.add(Restrictions.eq("login",login);
        and.add(Restrictions.eq("senha",senha);
        and.add(Restrictions.eq("ativo",true);
        return and;
    }
}
O seu repositório (DAO) seria assim:

public class RepositorioUsuarios {

    public List recuperarPorEspecificacao(Especificacao espec) {

        Criteria criteria = session.createCriteria(Usuario.class);
        Criteria.add(espec.configurarRestricao(criteria,Usuario.class));
        return criteria.list();
    }
}
Agora você tem um Especificacao que filtra os registros no banco fazendo com que a criteria seja configurada fora do repositório.

Para usar para validação seria assim:

Especificacao loginValido = new EspecificacaoLoginValido("carlos","123");
Usuario usuario = new Usuario();
usuario.setLogin("Carlos");
usuario.setSenha("123");
usuario.setAtivo(true);

loginValido.satisfaz(usuario) // true login, senha e ativo
Para ser executada sobre o repositório seria assim

RepositorioUsuario usuarios = new RepositorioUsuario();
List<usuario> listagem = usuarios.recuperarPorEspecificacao(loginValido);

Isto recupera uma listagem de usuários que safisfazem a especificação.

Este é um exemplo de como usar.

Depois em um outro post explicarei como usar o padrão para casar filtros com o hibernate, seria algo como o exemplo abaixo:

EspecificacaoFaturasVencidas
EspecificacaoClientesInadiplenentes

Como usá-las em conjunto?

Isto fica para outro post.


Nenhum comentário :