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 :
Postar um comentário