Estou buscando um conjunto de tuplas no banco de dados e colocando-o em um mapa. A consulta ao banco de dados é cara.
Não há uma ordem natural óbvia dos elementos no mapa, mas a ordem de inserção é importante. A classificação do mapa seria uma operação pesada, por isso quero evitar fazer isso, já que o resultado da consulta já está classificado da maneira que eu quero. Portanto, apenas guardo o resultado da consulta em um LinkedHashMap
e retorno o mapa de um método DAO:
public LinkedHashMap<Key, Value> fetchData()
Eu tenho um método processData
que deve fazer algum processamento no mapa - modificando alguns valores, adicionando algumas novas chaves / valores. É definido como
public void processData(LinkedHashMap<Key, Value> data) {...}
No entanto, vários linters (Sonar etc) reclamam que O tipo de 'dados' deve ser uma interface como 'Mapa', em vez da implementação "LinkedHashMap" ( squid S1319 ).
Então, basicamente, está dizendo que eu deveria ter
public void processData(Map<Key, Value> data) {...}
Mas quero que a assinatura do método diga que a ordem do mapa é importante - é importante para o algoritmo processData
- para que meu método não seja passado apenas a qualquer mapa aleatório.
Eu não quero usar SortedMap
, porque (do javadoc dejava.util.SortedMap
) "é ordenado de acordo com a ordem natural de suas chaves ou por um Comparador normalmente fornecido no momento da criação do mapa classificado".
Minhas chaves não têm uma ordem natural e criar um Comparador para não fazer nada parece detalhado.
E eu ainda gostaria que fosse um mapa, para tirar vantagem de put
evitar chaves duplicadas, etc. Se não, data
poderia ter sido a List<Map.Entry<Key, Value>>
.
Então, como digo que meu método deseja um mapa que já esteja classificado ? Infelizmente, não há java.util.LinkedMap
interface, ou eu teria usado isso.
fonte
if you are new to programming and stumble upon this answer, don't think this allows you to go against best practice because it doesn't.
- Bom conselho, se houver algo como "melhores práticas". Melhor conselho: aprenda a tomar as decisões corretas. Siga a prática se fizer sentido, mas deixe que as ferramentas e as autoridades guiem seu processo de pensamento, não o ditem.Você está lutando com três coisas:
Primeiro é a biblioteca de contêineres do Java. Nada em sua taxonomia fornece uma maneira de determinar se a classe itera ou não em uma ordem previsível. Não há
IteratesInInsertedOrderMap
interface que possa ser implementadaLinkedHashMap
, o que torna impossível a verificação de tipo (e o uso de implementações alternativas que se comportam da mesma maneira). Isso é provavelmente por design, porque o espírito disso é que você realmente deve ser capaz de lidar com objetos que se comportam como o abstratoMap
.A segunda é a crença de que o que o seu interlocutor diz deve ser tratado como evangelho e que ignorar tudo o que diz é ruim. Ao contrário do que se passa com as boas práticas nos dias de hoje, os avisos intermediários não devem ser barreiras para chamar seu código de bom. São instruções para raciocinar sobre o código que você escreveu e usar sua experiência e julgamento para determinar se o aviso é ou não justificado. Avisos injustificados são o motivo pelo qual quase todas as ferramentas de análise estática fornecem um mecanismo para informar que você examinou o código, acha que está fazendo tudo bem e que não deve reclamar no futuro.
Terceiro, e essa provavelmente é a carne,
LinkedHashMap
pode ser a ferramenta errada para o trabalho. Os mapas destinam-se a acesso aleatório, não ordenado. SeprocessData()
simplesmente itera sobre os registros em ordem e não precisa encontrar outros registros por chave, você está forçando uma implementação específicaMap
a fazer o trabalho de aList
. Por outro lado, se você precisar de ambos,LinkedHashMap
é a ferramenta certa, porque é conhecido por fazer o que você deseja e você tem mais do que justificativa em exigi-lo.fonte
OrderedMap
, eu poderia muito bem dizerUniqueList
. Desde que seja algum tipo de coleção com uma ordem de iteração definida, que substitua duplicatas na inserção.Set
apenas as chaves enquanto cria a lista como uma maneira de identificá-las.processData
modifica o mapa, substituindo alguns valores, introduzindo algumas novas chaves / valores. Assim,processData
poderia introduzir duplicatas se estivesse operando em algo diferente de aMap
.UniqueList
(ouOrderedUniqueList
) e usá-lo. É bem fácil e facilita o uso pretendido.Se tudo o que você obtém
LinkedHashMap
é a capacidade de sobrescrever duplicatas, mas você realmente a usa como umaList
, sugiro que seja melhor comunicar esse uso com sua própriaList
implementação personalizada . Você pode baseá-lo em uma classe de coleções Java existente e simplesmente substituir qualqueradd
eremove
métodos para atualizar seu armazenamento de backup e manter o controle da chave para garantir a exclusividade. Atribuir a esse nome um nome distintoProcessingList
tornará claro que os argumentos apresentados ao seuprocessData
método precisam ser tratados de uma maneira específica.fonte
ProcessingList
como um alias paraLinkedHashMap
- você sempre pode optar por substituí-lo por algo mais tarde, desde que mantenha a interface pública intacta.Estou ouvindo você dizer "Tenho uma parte do meu sistema que produz um LinkedHashMap e, em outra parte do meu sistema, preciso aceitar apenas objetos do LinkedHashMap que foram produzidos pela primeira parte, já que os produzidos por outro processo venceram" t funciona corretamente. "
Isso me faz pensar que o problema aqui é que você está tentando usar o LinkedHashMap, pois ele se encaixa principalmente nos dados que você está procurando, mas, na verdade, não pode ser substituído por nenhuma outra instância que não seja a que você cria. O que você realmente deseja fazer é criar sua própria interface / classe, que é o que sua primeira parte cria e sua segunda parte consome. Ele pode agrupar o LinkedHashMap "real" e fornecer um getter de mapa ou implementar a interface do mapa.
Isso é um pouco diferente da resposta de CandiedOrange, pois eu recomendaria encapsular o mapa real (e delegar chamadas, conforme necessário), em vez de estendê-lo. Às vezes é uma daquelas guerras santas do estilo, mas com certeza me parece que não é "Um mapa com algumas coisas adicionais", é "Minha bolsa de informações úteis sobre o estado, que eu posso representar internamente com um mapa".
Se você tivesse duas variáveis que precisaria repassar assim, provavelmente teria feito uma aula para ela sem pensar muito sobre isso. Mas às vezes é útil ter uma classe, mesmo que seja apenas uma variável de membro, apenas porque é logicamente a mesma coisa, não um "valor", mas "o resultado da minha operação com a qual preciso fazer as coisas mais tarde".
fonte
MyBagOfUsefulInformation
seria necessário um método (ou construtor) para preenchê-lo:MyBagOfUsefulInformation.populate(SomeType data)
. Masdata
precisaria ser o resultado da consulta classificada. Então, o que seriaSomeType
, se nãoLinkedHashMap
? Não tenho certeza se sou capaz de quebrar essa captura 22.MyBagOfUsefulInformation
ser criado pelo DAO ou o que quer que esteja gerando os dados em seu sistema? Por que você precisa expor o mapa subjacente ao resto do seu código fora do produtor e consumidor do Bag?MyBagOfUsefulInformation
como um parâmetro para o método DAO: softwareengineering.stackexchange.com/a/360079/52573O LinkedHashMap é o único mapa java que possui o recurso de pedido de inserção que você está procurando. Portanto, descartar o Princípio da Inversão da Dependência é tentador e talvez até prático. Primeiro, considere o que seria necessário para segui-lo. Aqui está o que o SOLID solicitaria que você fizesse.
Nota: substitua o nome
Ramdal
por um nome descritivo que comunique que o consumidor desta interface é o proprietário dessa interface. O que torna a autoridade que decide se o pedido de inserção é importante. Se você simplesmente chamar isso,InsertionOrderMap
realmente perdeu o ponto.Esse é um grande projeto antecipadamente? Talvez, dependa da probabilidade de você precisar de uma implementação além disso
LinkedHashMap
. Mas se você não está seguindo o DIP apenas porque seria uma dor enorme, não acho que a placa da caldeira seja mais dolorosa do que isso. Esse é o padrão que eu uso quando desejo que o código intocável implemente uma interface que ele não faz. A parte mais dolorosa é realmente pensar em bons nomes.fonte
Obrigado por muitas sugestões e bons pensamentos.
Acabei estendendo a criação de uma nova classe de mapa, criando
processData
um método de instância:Em seguida, refatorei o método DAO para que ele não retorne um mapa, mas, em vez disso, pega um
target
mapa como parâmetro:Portanto, preencher
DataMap
e processar os dados agora é um processo de duas etapas, o que é bom, já que existem outras variáveis que fazem parte do algoritmo, provenientes de outros lugares.Isso permite que minha implementação de mapa controle como as entradas são inseridas e oculta os requisitos de pedido - agora é um detalhe de implementação
DataMap
.fonte
Se você deseja comunicar que a estrutura de dados que você usou existe por um motivo, adicione um comentário acima da assinatura do método. Se outro desenvolvedor, no futuro, encontrar essa linha de código e perceber um aviso de ferramenta, ele poderá observar o comentário e evitar "corrigir" o problema. Se não houver comentário, nada os impedirá de alterar a assinatura.
Suprimir avisos é inferior a comentar na minha opinião, porque a supressão em si não indica o motivo pelo qual o aviso foi suprimido. Uma combinação de supressão de aviso e comentário também será adequada.
fonte
Então, deixe-me tentar entender seu contexto aqui:
Agora, o que você já está fazendo no momento:
E aqui está o seu código atual:
Minha sugestão é fazer o seguinte:
Exemplo de código
Eu acho que isso eliminaria o aviso do Sonar e também especificaria no layout específico da assinatura dos dados exigidos pelo método de processamento.
fonte
MyTupleRepository
é criado?)Esta questão é na verdade um monte de problemas com o seu modelo de dados agrupado em um. Você precisa começar a desembaraçá-los, um de cada vez. Soluções mais naturais e intuitivas desaparecerão quando você tentar simplificar cada peça do quebra-cabeça.
Problema 1: você não pode depender da ordem do banco de dados
Suas descrições de classificação de seus dados não são claras.
ORDER BY
cláusula. Se você não é porque parece muito caro, seu programa tem um bug . Os bancos de dados podem retornar resultados em qualquer ordem, se você não especificar um; você não pode confiar no retorno coincidente de dados no pedido, apenas porque você executou a consulta algumas vezes e parece que sim. A ordem pode mudar porque as linhas são reorganizadas no disco, ou algumas são excluídas e novas são substituídas ou um índice é adicionado. Você deve especificar umaORDER BY
cláusula de algum tipo. A velocidade é inútil sem correção.ORDER BY
cláusula. Caso contrário, você tem bugs. Se essa coluna ainda não existir, você precisará adicionar uma. Opções típicas para colunas como essa seriam uma coluna de carimbo de data / hora de inserção ou uma chave de incremento automático. A chave de incremento automático é mais confiável.Problema 2: Tornando a classificação de memória eficiente
Depois de garantir a devolução dos dados na ordem esperada, você pode aproveitar esse fato para tornar os tipos de memória muito mais eficientes. Basta adicionar um
row_number()
oudense_rank()
coluna (ou equivalente de seu banco de dados) para definir o resultado de sua consulta. Agora, cada linha tem um índice que fornece uma indicação direta do que o pedido deve ser, e você pode classificá-lo na memória trivialmente. Apenas certifique-se de fornecer um nome significativo ao índice (comosortedBySomethingIndex
).Viola. Agora você não precisa mais depender da ordem do conjunto de resultados do banco de dados.
Problema 3: Você precisa fazer esse processamento no código?
SQL é realmente muito poderoso. É uma linguagem declarativa incrível que permite fazer muitas transformações e agregações em seus dados. Atualmente, a maioria dos bancos de dados ainda suporta operações entre linhas. Eles são chamados de janela ou funções analíticas:
OVER
Cláusula do SQL Server para funções da janelaVocê precisa extrair seus dados para a memória assim? Ou você poderia fazer todo o trabalho na consulta SQL usando as funções da janela? Se você pode fazer todo (ou talvez apenas uma parte significativa) do trabalho no DB, fantástico! Seu problema de código desaparece (ou fica muito mais simples)!
Problema 4: Você está fazendo o que para isso
data
?Supondo que você não possa fazer tudo isso no banco de dados, deixe-me ver se entendi. Você está tomando os dados como mapa (que é codificado por itens que você não deseja classificar), depois iterando sobre eles na ordem de inserção e modificando o mapa no local, substituindo o valor de algumas chaves e adicionando novos?
Sinto muito, mas que diabos?
Os chamadores não precisam se preocupar com tudo isso . O sistema que você criou é extremamente frágil. É preciso apenas um erro estúpido (talvez até cometido por você mesmo, como todos nós fizemos) para fazer uma pequena alteração errada e a coisa toda desmorona como um baralho de cartas.
Aqui está talvez uma ideia melhor:
List
.Uma variação possível poderia ser construir uma representação classificada e criar um mapa de chave para indexar . Isso permitiria modificar sua cópia classificada no local, sem criar duplicatas acidentalmente.
Ou talvez isso faça mais sentido: livrar-se do
data
parâmetro eprocessData
realmente buscar seus próprios dados. Em seguida, você pode documentar que está fazendo isso porque ele possui requisitos muito específicos sobre a maneira como os dados são buscados. Em outras palavras, torne a função proprietária de todo o processo, não apenas uma parte dele; as interdependências são fortes demais para dividir a lógica em partes menores. (Altere o nome da função no processo.)Talvez isso não funcione para a sua situação. Eu não sei sem detalhes completos do problema. Mas conheço um design frágil e confuso quando o ouço.
Sumário
Penso que o problema aqui é, em última análise, que o diabo está nos detalhes. Quando começo a ter problemas como esse, geralmente é porque tenho uma representação inadequada dos meus dados para o problema que estou tentando resolver. A melhor solução é encontrar uma melhor representação e, em seguida, meu problema se torna simples (talvez não fácil, mas direto) de resolver.
Encontre alguém que entenda esse ponto: seu trabalho é reduzir seu problema a um conjunto de problemas simples e diretos. Então você pode criar um código robusto e intuitivo. Fale com eles. Um bom código e um bom design fazem você pensar que qualquer idiota poderia ter pensado neles, porque são simples e diretos. Talvez haja um desenvolvedor sênior com essa mentalidade com a qual você possa conversar.
fonte
select key, value from table where ... order by othercolumn
e precisa manter a ordem em seu processamento. A ordem de inserção a que eles estão se referindo é a ordem de inserção em seu mapa , definida pelo pedido usado em sua consulta, não a ordem de inserção no banco de dados . Isso fica claro pelo uso deLinkedHashMap
, que é uma estrutura de dados que possui as características de aMap
e de umList
dos pares de valores-chave.order by
cláusula na consulta, mas não é trivial ( não apenasorder by column
), assim que eu quero evitar reimplementar a classificação em Java. Embora o SQL seja poderoso (e estamos falando de um banco de dados Oracle 11g aqui), a natureza doprocessData
algoritmo facilita muito a expressão em Java. E sim, "pedido de inserção" significa " pedido de inserção de mapa ", ou seja, pedido de resultado da consulta.