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