Reuso de Código em Java com MapParams

Veja neste artigo como aumentar a Reusabilidade e Produtividade do seu projeto com uma classe MapParams em Java.

Neste artigo veremos como criar uma forma prática de parametrização em Java, convertendo uma lista de parâmetros, com tamanho indefinido, para um Map de fácil manipulação. Antes vamos entender o cenário do nosso problema para saber a real necessidade de utilização deste recurso.

É muito comum, principalmente quando estamos trabalhando com NamedQueries do JPA, a passagem de parâmetros de quantidade indefinida, sem nomeação ou tipagem específica. Isso nos da a possibilidade de criar métodos genéricos o suficiente para executar qualquer Query do JPA, independente da quantidade de parâmetros exigida ou mesmo do tipo de cada parâmetro. Vamos supor, por exemplo, os seguintes HQL's:

  1. “SELECT * FROM Pessoa p WHERE p.nome = :nome AND p.idade = :idade
  2. “SELECT * FROM Produto p WHERE p.valor = :valor AND p.dataValidade = :dataValidade

Temos 2 HQL's acima, cada um com dois parâmetros mas com tipos e nomes distintos. A solução mais elegante e correta seria criar um método recebendo um Map de parâmetros como o Generic . Assim conseguimos trabalhar de forma genérica suficiente e não precisamos criar um método para cada tipo de HQL que surgir.

É muito comum projetos trabalharem recebendo um Map de Parâmetros, tornando a produtividade muito melhor, e não só para o HQL, mas para todas as outras situações em que os parâmetros devem ser abstraídos. Porém, a forma com que esses parâmetros são passados é que diferem de projeto para projeto, conforme veremos como neste artigo.

Baseado na Listagem 1 temos um método simples que recebe um Map de parâmetros. Vamos ver comumente como este Map pode ser passado.

import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; public class MyApp { public static void main (String[] args){ /* * Vamos começar com um exemplo usando a forma comum de passar um Map de params * para um método. * */ Map<String,Integer> mapParams = new HashMap<String,Integer>(); //Vamos supor a criação de alguns parâmetros, apenas para exemplo mapParams.put("idadeMinima", 15); mapParams.put("idadeMaxima", 60); mapParams.put("quantidadeMaxima", 450); mapParams.put("quantidadeMinima", 1); mapParams.put("fatorDeDistribuicao", 3); mostraParametrizacao(mapParams); } private static void mostraParametrizacao(Map<String,Integer> params){ for(Entry<String, Integer> entry : params.entrySet()){ String key = entry.getKey(); Integer value = entry.getValue(); System.out.println("Key = "+key+", Value = "+value); } } }
Listagem 1. Passando um Map de Parâmetros

Normal, certo? Qualquer um faria a mesma coisa, mas em um projeto com milhares de linhas onde a utilização de parâmetros via Map é muito frequente. Você vai rapidamente perceber que ficar criando um HashMap e usando put toda vez irá demandar muito tempo e tornar seu código muito poluído visualmente. Veja na Listagem 2 qual seria o ideal para situações de frequente utilização deste recurso.

import java.util.Map; import java.util.Map.Entry; public class MyApp { public static void main (String[] args){ mostraParametrizacao(new MapParams("idadeMinima", 15, "idadeMaxima", 60, "quantidadeMaxima", 450, "quantidadeMinima", 1, "fatorDeDistribuicao", 3)); } private static void mostraParametrizacao(Map<String,Object> params){ for(Entry<String, Object> entry : params.entrySet()){ String key = entry.getKey(); Object value = entry.getValue(); System.out.println("Key = "+key+", Value = "+value); } } }
Listagem 2. Uso adequado

Reduzimos consideravelmente a complexidade e a quantidade de linhas da nossa classe, veja como ficou simples e prático realizar a passagem de parâmetros para Map. Apenas tivemos que mudar o tipo do nosso Map de para , por exigência da nossa classe MapParams que torna tudo o mais genérico possível. Veremos mais a frente detalhes disso.

Então neste ponto vimos a facilidade que este artigo está propondo a você leitor, o uso constante de parâmetros via Map com uma simples classe que irá gerenciar tudo isso. Vamos utilizar a “Reusabilidade” ao nosso favor.

A classe MapParams

Entendido o problema principal, vamos agora aprofundar-nos na classe MapParams, afinal é ela quem fará toda a “mágica” de conversão sem que precisemos nos preocupar com instanciação de HashMap a cada momento. Na Listagem 3 nossa classe MapParams está toda comentada, linha a linha, facilitando assim a compreensão da mesma.

import java.io.Serializable; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; /* * Nossa classe MapParams é responsável por converter nossos parâmetros * para um Map, assim podemos realizar operações comuns como clear, contains, * put e assim por diante. * * */ public class MapParams implements Map<String, Object>, Serializable { private static final long serialVersionUID = 1L; /* * Este é o objetivo convertido, ou seja, é neste objeto que todos os parâmetros serão * mapeados e inseridos. Toda operação final será realizada com este objeto. * */ private Map<String, Object> paramsAsMap; /* * Nosso construtor recebe uma lista indefinida de parâmetros e chama o método * "inserirParametros(parametros)" que é responsável por realizar * explicitamente a conversão para Map. * */ public MapParams(Object... parametros) { inserirParametros(parametros); } @SuppressWarnings("unchecked") public MapParams inserirParametros(Object... parametros) { /* * Como trata-se de um mapeamento 1 para 1, onde cada String (key) * terá seu respectivo valor (value), formando um par 'key-value', * então é obrigatório que a quantidade de parâmetros seja par. * */ if (parametros.length % 2 != 0) throw new IllegalArgumentException ("Espera-se número par de objetos: " + parametros.length); /* * Verificamos se nosso objeto Map já foi criado, caso contrário * criamos um novo Map com a mesma quantidade dos parâmetros * passados divido por 2, isso porque a cada 2 valores formamos * 1 novo item no Map. Lembre-se do par key-value, * isso define como nosso Map funciona. * */ if (this.paramsAsMap == null) this.paramsAsMap = new HashMap(parametros.length / 2); /* * Neste ponto a conversão começa. Criamos um laço for de zero (0) até * a quantidade de parâmetros, incrementando de 2 em 2. * */ for (int i = 0; i < parametros.length; i += 2) { /* * Como nosso laço irá incrementar de 2 em 2 e começamos do zero, * então sempre pegaremos um valor par, que corresponde ao * 'key' do Map, e o valor impar corresponde ao * 'value' do Map. * Por exemplo: {'NOME', 'Devmedia','TIPO','Empresa'}. * Na lista ao lado temos as keys NOME e TIPO, * e os seus respectivos valores 'Devmedia' e 'Empresa'. * Perceba que sempre o Key é par e o Value * é impar. * * */ Object key = parametros[i]; /* * Por obrigação nosso 'Key' tem que ser sempre uma String, * não faz sentido que seja de outra forma. * Então verificamos de o objeto key é uma instancia da classe String, * caso contrário ele * lançará uma exceção abortando a continuação da conversão. * */ if (! (key instanceof String)) throw new IllegalArgumentException("O parametro '" + i + "' deveria ser uma String."); /* * Caso o valor de i+1 seja menor que a quantidade total * de parâmetros, então retornamos o valor do parâmetro no índice i+1, * que é igual a um valor impar, já que nosso i sempre será par, * e um valor impar corresponde sempre ao nosso 'value' no Map. * Retornado o 'value', agora já temos o objeto key e o * objeto value, basta usarmos o 'put' * para inserir esse par em nosso objeto Map. * */ Object value = (i + 1 < parametros.length ? parametros[i + 1] : null); paramsAsMap.put(key.toString(), value); } /* * Neste ponto, toda conversão já está pronta, então o que * retornaremos é a classe MapParams, * e não apenas o objeto paramAsMap. * */ return this; } /* * Todos os métodos abaixo são padrões do Map, e como o mesmo é uma Interface, somos * obrigados a implementá-los. * */ public void clear() { paramsAsMap.clear(); } public boolean containsKey(Object key) { return paramsAsMap.containsKey(key); } public boolean containsValue(Object value) { return paramsAsMap.containsValue(value); } public Set<Entry<String, Object>> entrySet() { return paramsAsMap.entrySet(); } public Object get(Object key) { return paramsAsMap.get(key); } public boolean isEmpty() { return paramsAsMap.isEmpty(); } public Set<String> keySet() { return paramsAsMap.keySet(); } public Object put(String key, Object value) { return paramsAsMap.put(key, value); } public void putAll(Map<? extends String, ? extends Object> t) { paramsAsMap.putAll(t); } public Object remove(Object key) { return paramsAsMap.remove(key); } public int size() { return paramsAsMap.size(); } public Collection<Object> values() { return paramsAsMap.values(); } @Override public String toString() { return paramsAsMap.toString(); } }
Listagem 3. MapParams.java

O principal objetivo deste artigo foi primeiramente demonstrar o cenário onde o problema é aplicado e a solução deste problema utilizando uma classe chamada MapParams, que pode ser nomeada para um nome de sua escolha. Sendo assim, aumentamos a Reusabilidade de código e consequentemente a produtividade do projeto.

Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados