Estoy buscando un conjunto de tuplas de la base de datos y poniéndolo en un mapa. La consulta de la base de datos es costosa.
No existe un orden natural obvio de los elementos en el mapa, pero el orden de inserción es importante. Ordenar el mapa sería una operación pesada, por lo que quiero evitar hacerlo, dado que el resultado de la consulta ya está ordenado de la manera que lo quiero. Por lo tanto, solo almaceno el resultado de la consulta en a LinkedHashMap
y devuelvo el mapa desde un método DAO:
public LinkedHashMap<Key, Value> fetchData()
Tengo un método processData
que debería procesar un poco en el mapa: modificar algunos valores y agregar nuevas claves / valores. Se define como
public void processData(LinkedHashMap<Key, Value> data) {...}
Sin embargo, varios linters (Sonar, etc.) se quejan de que el tipo de 'datos' debería ser una interfaz como 'Map' en lugar de la implementación "LinkedHashMap" ( squid S1319 ).
Básicamente está diciendo que debería tener
public void processData(Map<Key, Value> data) {...}
Pero quiero que la firma del método diga que el orden del mapa es importante , es importante para el algoritmo processData
, para que mi método no se pase a cualquier mapa aleatorio.
No quiero usarlo SortedMap
porque (del javadoc dejava.util.SortedMap
) "se ordena de acuerdo con el orden natural de sus claves, o por un Comparador que normalmente se proporciona en el momento de la creación del mapa ordenado".
Mis claves no tienen un orden natural , y crear un Comparador para no hacer nada parece detallado.
Y todavía quisiera que fuera un mapa, para aprovecharlo put
y evitar claves duplicadas, etc. Si no, data
podría haber sido un List<Map.Entry<Key, Value>>
.
Entonces, ¿cómo puedo decir que mi método quiere un mapa que ya está ordenado ? Lamentablemente, no hay java.util.LinkedMap
interfaz, o habría usado eso.
fuente
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.
- Un buen consejo, si existiera la "mejor práctica". Un mejor consejo: aprenda a tomar las decisiones correctas. Siga la práctica si tiene sentido, pero deje que las herramientas y las autoridades guíen su proceso de pensamiento, no lo dicten.Estás luchando contra tres cosas:
Primero es la biblioteca de contenedores de Java. Nada en su taxonomía le brinda una forma de determinar si la clase itera o no en un orden predecible. No hay una
IteratesInInsertedOrderMap
interfaz que pueda implementarseLinkedHashMap
, lo que hace que la verificación de tipos (y el uso de implementaciones alternativas que se comporten de la misma manera) sea imposible. Probablemente sea por diseño, porque el espíritu es que realmente se supone que debes ser capaz de lidiar con objetos que se comportan como lo abstractoMap
.En segundo lugar, está la creencia de que lo que dice tu linter debe ser tratado como un evangelio y que ignorar todo lo que dice es malo. Al contrario de lo que pasa por las buenas prácticas en estos días, no se supone que las advertencias de linter sean barreras para llamar a su código correctamente. Son indicaciones para razonar sobre el código que ha escrito y utilizar su experiencia y criterio para determinar si la advertencia está justificada o no. Las advertencias injustificadas son la razón por la cual casi todas las herramientas de análisis estático proporcionan un mecanismo para decirle que ha examinado el código, cree que lo que está haciendo está bien y que no deberían quejarse en el futuro.
Tercero, y esto es probablemente el meollo de esto,
LinkedHashMap
puede ser la herramienta incorrecta para el trabajo. Los mapas están destinados al acceso aleatorio, no ordenado. SiprocessData()
simplemente itera sobre los registros en orden y no necesita encontrar otros registros por clave, está obligando a una implementación específica deMap
hacer el trabajo de aList
. Por otro lado, si necesita ambos,LinkedHashMap
es la herramienta adecuada porque se sabe que hace lo que quiere y está más que justificado para solicitarlo.fuente
OrderedMap
, también podría decirUniqueList
. Siempre que se trate de algún tipo de colección con un orden de iteración definido, que sobrescribe los duplicados en la inserción.Set
de solo las claves mientras construye la lista como una forma de detectarlas.processData
modifica el mapa, reemplaza algunos valores e introduce nuevas claves / valores. PorprocessData
lo tanto, podría introducir duplicados si estaba operando en algo diferente a aMap
.UniqueList
(oOrderedUniqueList
) y usarlo. Es bastante fácil y hace que su uso previsto sea más claro.Si todo lo que está obteniendo
LinkedHashMap
es la capacidad de sobrescribir duplicados, pero realmente lo está usando como unList
, entonces sugeriría que es mejor comunicar ese uso con su propiaList
implementación personalizada . Puede basar en una clase de colecciones Java existente y simplemente ignorar cualquieraadd
yremove
métodos para actualizar el almacén de respaldo y no perder de vista la clave para garantizar la unicidad. Darle a este un nombre distintivo comoProcessingList
dejará en claro que los argumentos presentados a suprocessData
método deben manejarse de una manera particular.fuente
ProcessingList
como un alias paraLinkedHashMap
: siempre puede decidir reemplazarlo por algo más tarde, siempre que mantenga intacta la interfaz pública.Te escucho decir: "Tengo una parte de mi sistema que produce un LinkedHashMap, y en otra parte de mi sistema necesito aceptar solo objetos LinkedHashMap que fueron producidos por la primera parte, ya que los producidos por algún otro proceso ganaron" no funciona correctamente ".
Eso me hace pensar que el problema aquí es en realidad que estás tratando de usar LinkedHashMap, ya que se ajusta principalmente a los datos que estás buscando, pero en realidad no se puede sustituir por ninguna otra instancia que las que creas. Lo que realmente quiere hacer es crear su propia interfaz / clase, que es lo que crea su primera parte y consume su segunda parte. Puede envolver el LinkedHashMap "real" y proporcionar un captador de mapas o implementar la interfaz del mapa.
Esto es un poco diferente de la respuesta de CandiedOrange, ya que recomendaría encapsular el Mapa real (y delegar llamadas según sea necesario) en lugar de extenderlo. A veces es una de esas guerras santas de estilo, pero seguro que me parece que no es "Un mapa con algunas cosas adicionales", es "Mi bolsa de información de estado útil, que puedo representar internamente con un mapa".
Si tuviera dos variables que necesitara transmitir de esta manera, probablemente habría hecho una clase sin pensar demasiado en ello. Pero a veces es útil tener una clase incluso si es solo una variable miembro, solo porque es lógicamente la misma cosa, no un "valor" sino "el resultado de mi operación con la que necesito hacer cosas más adelante".
fuente
MyBagOfUsefulInformation
necesitaría un método (o constructor) para poblarlo:MyBagOfUsefulInformation.populate(SomeType data)
. Perodata
tendría que ser el resultado de la consulta ordenada. Entonces, ¿qué seríaSomeType
, si noLinkedHashMap
? No estoy seguro de poder romper esta captura 22.MyBagOfUsefulInformation
el DAO no puede crearlo o lo que sea que esté generando los datos en su sistema? ¿Por qué necesita exponer el mapa subyacente al resto de su código fuera del productor y consumidor de la Bolsa?MyBagOfUsefulInformation
como parámetro al método DAO: softwareengineering.stackexchange.com/a/360079/52573LinkedHashMap es el único mapa de Java que tiene la función de orden de inserción que está buscando. Por lo tanto, descartar el principio de inversión de dependencia es tentador y quizás incluso práctico. Primero, sin embargo, considere lo que se necesitaría para seguirlo. Esto es lo que SOLID le pediría que haga.
Nota: reemplace el nombre
Ramdal
con un nombre descriptivo que comunique que el consumidor de esta interfaz es el propietario de esta interfaz. Lo que lo convierte en la autoridad que decide si el orden de inserción es importante. Si solo llamas a estoInsertionOrderMap
, realmente has perdido el punto.¿Es este un gran diseño por adelantado? Tal vez, depende de la probabilidad de que creas que necesitarás una implementación además
LinkedHashMap
. Pero si no está siguiendo DIP solo porque sería un gran dolor, no creo que la placa de la caldera sea más dolorosa que esto. Este es el patrón que uso cuando deseo que el código intocable implemente una interfaz que no lo hace. La parte más dolorosa es pensar en buenos nombres.fuente
Gracias por muchas buenas sugerencias y reflexiones.
Terminé extendiendo la creación de una nueva clase de mapa, haciendo
processData
un método de instancia:Luego refactoré el método DAO para que no devuelva un mapa, sino que tome un
target
mapa como parámetro:Por lo tanto, rellenar
DataMap
y procesar los datos ahora es un proceso de dos pasos, lo cual está bien, ya que hay algunas otras variables que forman parte del algoritmo, que proviene de otros lugares.Esto permite que la implementación de mi mapa controle cómo se insertan las entradas en él y oculta el requisito de pedido; ahora es un detalle de implementación
DataMap
.fuente
Si desea comunicar que la estructura de datos que utilizó está allí por algún motivo, agregue un comentario sobre la firma del método. Si otro desarrollador en el futuro se encuentra con esta línea de código y nota una advertencia de herramienta, también podrían notar el comentario y abstenerse de "solucionar" el problema. Si no hay ningún comentario, nada les impedirá cambiar la firma.
Suprimir advertencias es inferior a comentar en mi opinión, porque la supresión en sí misma no indica la razón por la cual se suprimió la advertencia. Una combinación de supresión de advertencia y comentario también estará bien.
fuente
Entonces, déjame tratar de entender tu contexto aquí:
Ahora, lo que estás haciendo actualmente:
Y aquí está tu código actual:
Mi sugerencia es hacer lo siguiente:
Ejemplo de código
Supongo que esto eliminaría la advertencia de Sonar y también especificaría en el diseño específico de datos de la firma requerido por el método de procesamiento.
fuente
MyTupleRepository
se crea?)Esta pregunta es en realidad un montón de problemas con su modelo de datos en uno. Debes comenzar a desenredarlos, uno a la vez. Las soluciones más naturales e intuitivas desaparecerán a medida que intente simplificar cada pieza del rompecabezas.
Problema 1: no puede depender del pedido de DB
Sus descripciones para ordenar sus datos no son claras.
ORDER BY
cláusula. Si no lo eres porque parece demasiado caro, tu programa tiene un error . Las bases de datos pueden devolver resultados en cualquier orden si no especifica uno; no puede depender de que coincida devolviendo datos en el orden solo porque ejecutó la consulta varias veces y se ve de esa manera. El orden puede cambiar porque las filas se reorganizan en el disco, o algunas se eliminan y otras nuevas toman su lugar, o se agrega un índice. Usted debe especificar unaORDER BY
cláusula de algún tipo. La velocidad no tiene valor sin corrección.ORDER BY
cláusula. De lo contrario, tienes errores. Si dicha columna aún no existe, entonces debe agregar una. Las opciones típicas para columnas como esta serían una columna de marca de tiempo de inserción o una clave de incremento automático. La clave de incremento automático es más confiable.Problema 2: hacer que la memoria sea eficiente
Una vez que se asegure de que se garantiza que devolverá los datos en el orden que espera, puede aprovechar este hecho para hacer que los tipos de memoria sean mucho más eficientes. Simplemente agregue una columna
row_number()
odense_rank()
(o el equivalente de su base de datos) al conjunto de resultados de su consulta. Ahora cada fila tiene un índice que le dará una indicación directa de lo que se supone que es el orden, y puede ordenarlo en memoria de manera trivial. Solo asegúrese de darle al índice un nombre significativo (comosortedBySomethingIndex
).Viola. Ahora ya no tiene que depender del orden del conjunto de resultados de la base de datos.
Problema 3: ¿Necesitas hacer este procesamiento en código?
SQL es realmente realmente poderoso. Es un lenguaje declarativo sorprendente que le permite realizar muchas transformaciones y agregaciones en sus datos. La mayoría de los DB incluso admiten operaciones de fila cruzada hoy en día. Se llaman ventanas o funciones analíticas:
OVER
Cláusula de SQL Server para funciones de ventana¿Incluso necesita extraer sus datos en la memoria de esta manera? ¿O podría hacer todo el trabajo en la consulta SQL utilizando funciones de ventana? Si puede hacer todo (o tal vez solo una parte importante) del trabajo en el DB, ¡fantástico! ¡Su problema de código desaparece (o se vuelve mucho más simple)!
Problema 4: ¿Estás haciendo qué
data
?Asumiendo que no puedes hacerlo todo en el DB, déjame aclarar esto. Está tomando los datos como un mapa (que está codificado por cosas por las que no desea ordenar), luego está iterando sobre ellos en orden de inserción y modificando el mapa en su lugar reemplazando el valor de algunas teclas y agregando ¿nuevos?
Lo siento, pero ¿qué diablos?
Las personas que llaman no deberían tener que preocuparse por todo esto . El sistema que ha creado es extremadamente frágil. Solo se necesita un error tonto (tal vez incluso hecho por usted mismo, como todos hemos hecho) para hacer un pequeño cambio incorrecto y todo se derrumba como una baraja de cartas.
Aquí quizás una mejor idea:
List
.Una posible variación podría ser construir una representación ordenada y luego crear un mapa de clave para indexar . Esto le permitiría modificar su copia ordenada en su lugar, sin crear duplicados accidentalmente.
O tal vez esto tiene más sentido: deshacerse del
data
parámetro y hacer queprocessData
realmente obtenga sus propios datos. Luego puede documentar que está haciendo esto porque tiene requisitos muy específicos sobre la forma en que se obtienen los datos. En otras palabras, haga que la función sea propietaria de todo el proceso, no solo de una parte; Las interdependencias son demasiado fuertes para dividir la lógica en fragmentos más pequeños. (Cambie el nombre de la función en el proceso).Quizás estos no funcionen para su situación. No lo sé sin todos los detalles del problema. Pero sí conozco un diseño frágil y confuso cuando escucho uno.
Resumen
Creo que el problema aquí es, en última instancia, que el diablo está en los detalles. Cuando empiezo a tener problemas como este, generalmente es porque tengo una representación inapropiada de mis datos para el problema que estoy tratando de resolver. La mejor solución es encontrar una mejor representación , y luego mi problema se vuelve simple (tal vez no fácil, pero sencillo) de resolver.
Encuentre a alguien que obtenga ese punto: su trabajo es reducir su problema a un conjunto de problemas simples y directos. Entonces puede construir código robusto e intuitivo. Habla con ellos. Un buen código y un buen diseño te hacen pensar que cualquier idiota podría haberlos pensado, porque son simples y directos. Tal vez hay un desarrollador senior que tiene esa mentalidad con la que puedes hablar.
fuente
select key, value from table where ... order by othercolumn
, y necesita mantener el orden en su procesamiento. El orden de inserción al que se refieren es el orden de inserción en su mapa , definido por el orden utilizado en su consulta, no el orden de inserción en la base de datos . Esto queda claro por su uso deLinkedHashMap
, que es una estructura de datos que tiene las características tanto de aMap
como de aList
de pares clave-valor.order by
cláusula en la consulta, pero no es trivial ( no soloorder by column
), por lo que quiero evitar reimplementar la clasificación en Java. Aunque SQL es poderoso (y estamos hablando de una base de datos Oracle 11g aquí), la naturaleza delprocessData
algoritmo hace que sea mucho más fácil de expresar en Java. Y sí, "orden de inserción" significa " orden de inserción del mapa ", es decir, orden de resultado de la consulta.