web-dev-qa-db-fra.com

Comment communiquer que l'ordre d'insertion est important dans une carte?

Je récupère un ensemble de tuples dans la base de données et je le mets dans une carte. La requête de base de données est coûteuse.

Il n'y a pas naturel ordre évident des éléments dans la carte, mais ordre d'insertion importe quand même. Le tri de la carte serait une opération lourde, donc je veux éviter de le faire, étant donné que le résultat de la requête est déjà trié comme je le veux. Par conséquent, je stocke simplement le résultat de la requête dans un LinkedHashMap et retourne la carte à partir d'une méthode DAO:

public LinkedHashMap<Key, Value> fetchData()

J'ai une méthode processData qui devrait faire un peu de traitement sur la carte - modifier certaines valeurs, ajouter de nouvelles clés/valeurs. Il est défini comme

public void processData(LinkedHashMap<Key, Value> data) {...}

Cependant, plusieurs linters (Sonar etc.) se plaignent que Le type de 'données' devrait être une interface telle que 'Map' plutôt que l'implémentation "LinkedHashMap" ( calmar S1319 ).
Donc, fondamentalement, il dit que j'aurais dû

public void processData(Map<Key, Value> data) {...}

Mais je veux que la signature de la méthode indique que l'ordre des cartes est important - cela compte pour l'algorithme dans processData - afin que ma méthode ne soit pas transmise comme n'importe quelle carte aléatoire.

Je ne veux pas utiliser SortedMap, car il (du javadoc de Java.util.SortedMap ) "est ordonné selon le ordre naturel de ses clés, ou par un Comparateur généralement fourni au moment de la création de la carte triée."

Mes clés n'ont pas de ordre naturel, et créer un Comparateur pour ne rien faire semble verbeux.

Et je voudrais toujours que ce soit une carte, pour profiter de put pour éviter les clés en double, etc. Sinon, data aurait pu être un List<Map.Entry<Key, Value>>.

Alors, comment puis-je dire que ma méthode veut une carte qui est déjà triée? Malheureusement, il n'y a pas de Java.util.LinkedMap interface, sinon je l'aurais utilisé.

24
Vidar S. Ramdal

Merci pour beaucoup de bonnes suggestions et de matière à réflexion.

J'ai fini par étendre la création d'une nouvelle classe de carte, faisant de processData une méthode d'instance:

class DataMap extends LinkedHashMap<Key, Value> {

   processData();

}

J'ai ensuite refactorisé la méthode DAO pour qu'elle ne retourne pas de carte, mais prend à la place une carte target comme paramètre:

public void fetchData(Map<Key, Value> target) {
  ...
  // for each result row
  target.put(key, value);
}

Donc, remplir un DataMap et traiter les données est maintenant un processus en deux étapes, ce qui est bien, car il y a d'autres variables qui font partie de l'algorithme, qui viennent d'autres endroits.

public DataMap fetchDataMap() {
  var dataMap = new DataMap();
  dao.fetchData(dataMap);
  return dataMap;
}

Cela permet à mon implémentation Map de contrôler la façon dont les entrées y sont insérées, et masque les exigences de classement - c'est maintenant un détail d'implémentation de DataMap.

1
Vidar S. Ramdal

Utilisez donc LinkedHashMap.

Oui, vous devriez utiliser Map sur une implémentation spécifique chaque fois que possible, et oui, cette est meilleure pratique.

Cela dit, il s'agit d'une situation étrangement spécifique où l'implémentation de Map est réellement importante. Cela ne sera pas vrai pour 99,9% des cas dans votre code lorsque vous utilisez Map, et pourtant vous êtes ici, dans cette situation de 0,1%. Sonar ne peut pas le savoir et Sonar vous dit simplement d'éviter d'utiliser l'implémentation spécifique car elle serait correcte dans la plupart des cas.

Je dirais que si vous pouvez justifier l'utilisation d'une implémentation spécifique, n'essayez pas de mettre du rouge à lèvres sur un cochon. Vous avez besoin d'un LinkedHashMap, pas d'un Map.

Cela dit, si vous êtes nouveau dans la programmation et tombez sur cette réponse, ne pensez pas que cela vous permet d'aller à l'encontre des meilleures pratiques, car ce n'est pas le cas. Mais lorsque le remplacement d'une implémentation par une autre n'est pas acceptable, la seule chose que vous pouvez faire est d'utiliser cette implémentation spécifique et d'être damné envers Sonar.

56
Neil

Vous vous battez contre trois choses:

La première est la bibliothèque de conteneurs de Java. Rien dans sa taxonomie ne vous donne un moyen de déterminer si la classe itère ou non dans un ordre prévisible. Il n'y a pas d'interface IteratesInInsertedOrderMap qui pourrait être implémentée par LinkedHashMap, ce qui rend impossible la vérification de type (et l'utilisation d'implémentations alternatives qui se comportent de la même manière). C'est probablement par conception, car l'esprit de celui-ci est que vous êtes vraiment censé pouvoir traiter des objets qui se comportent comme l'abstrait Map.

Deuxièmement, nous croyons que ce que dit votre interlocuteur doit être traité comme un évangile et qu'ignorer tout ce qu'il dit est mauvais. Contrairement à ce qui passe pour une bonne pratique de nos jours, les avertissements de linter ne sont pas censés être des barrières pour qualifier votre code de bon. Ils sont invités à raisonner sur le code que vous avez écrit et à utiliser votre expérience et votre jugement pour déterminer si l'avertissement est justifié ou non. Les avertissements injustifiés sont la raison pour laquelle presque tous les outils d'analyse statique fournissent un mécanisme pour leur dire que vous avez examiné le code, que vous pensez que ce que vous faites est correct et qu'ils ne devraient pas s'en plaindre à l'avenir.

Troisièmement, et c'est probablement la viande de celui-ci, LinkedHashMap peut être le mauvais outil pour le travail. Les cartes sont destinées à un accès aléatoire et non ordonné. Si processData() parcourt simplement les enregistrements dans l'ordre et n'a pas besoin de trouver d'autres enregistrements par clé, vous forcez une implémentation spécifique de Map à faire le travail d'un List. D'un autre côté, si vous avez besoin des deux, LinkedHashMap est le bon outil car il est connu pour faire ce que vous voulez et vous êtes plus que justifié de l'exiger.

21
Blrfl

Si tout ce que vous obtenez de LinkedHashMap est la possibilité d'écraser les doublons, mais que vous l'utilisez vraiment comme List, alors je suggère qu'il est préférable de communiquer cette utilisation avec la vôtre implémentation personnalisée de List. Vous pouvez le baser sur une classe de collections Java Java et remplacer simplement toutes les méthodes add et remove pour mettre à jour votre magasin de sauvegarde et garder une trace de la clé pour vous assurer Le fait de lui donner un nom distinctif comme ProcessingList indiquera clairement que les arguments présentés à votre méthode processData doivent être traités d'une manière particulière.

15
TMN

Je vous entends dire "J'ai une partie de mon système qui produit un LinkedHashMap, et dans une autre partie de mon système, je dois accepter uniquement les objets LinkedHashMap qui ont été produits par la première partie, car ceux produits par un autre processus ont gagné" t fonctionne correctement. "

Cela me fait penser que le problème ici est en fait que vous essayez d'utiliser LinkedHashMap car il correspond principalement aux données que vous recherchez, mais en fait, il ne peut pas être remplacé par une autre instance que celles que vous créez. Ce que vous voulez réellement faire, c'est créer votre propre interface/classe qui est ce que votre première partie crée et votre deuxième partie consomme. Il peut envelopper le "vrai" LinkedHashMap, et fournir un getter de carte ou implémenter l'interface de carte.

Ceci est un peu différent de la réponse de CandiedOrange, en ce que je recommanderais d'encapsuler la vraie carte (et de lui déléguer des appels si nécessaire) plutôt que de l'étendre. C'est parfois une de ces guerres sacrées de style, mais il me semble que ce n'est pas "une carte avec des trucs supplémentaires", c'est "mon sac d'informations utiles sur l'état, que je peux représenter en interne avec une carte".

Si vous aviez deux variables que vous aviez besoin de faire circuler comme ceci, vous auriez probablement fait une classe pour elle sans y penser beaucoup. Mais parfois, il est utile d'avoir une classe même si ce n'est qu'une variable membre, simplement parce que c'est logiquement la même chose, pas une "valeur" mais "le résultat de mon opération avec lequel je dois faire des choses plus tard".

11
user101289

LinkedHashMap est la seule carte Java qui possède la fonction d'ordre d'insertion que vous recherchez. Par conséquent, ignorer le principe d'inversion de dépendance est tentant et peut-être même pratique. Tout d'abord, réfléchissez à ce qu'il faudrait pour Voici ce que SOLID vous demanderait de faire.

Remarque: remplacez le nom Ramdal par un nom descriptif indiquant que le consommateur de cette interface est le propriétaire de cette interface. Ce qui en fait l'autorité qui décide si l'ordre d'insertion est important. Si vous appelez simplement cela InsertionOrderMap vous avez vraiment raté le point.

public interface Ramdal {
    //ISP asks for just the methods that processData() actually uses.
    ...
}

public class RamdalLinkedHashMap extends LinkedHashMap implements Ramdal{} 

Ramdal<Key, Value> ramdal = new RamdalLinkedHashMap<>();

ramdal.put(key1, value1);
ramdal.put(key2, value2);

processData(ramdal);

Est-ce un gros design à l'avant? Cela dépend peut-être de la probabilité que vous ayez besoin d'une implémentation en plus de LinkedHashMap. Mais si vous ne suivez pas DIP uniquement parce que ce serait une énorme douleur, je ne pense pas que la plaque de la chaudière soit plus douloureuse que cela. C'est le modèle que j'utilise lorsque je souhaite que le code intouchable implémente une interface qu'il ne fait pas. La partie la plus douloureuse est vraiment de penser à de bons noms.

4
candied_orange

Alors, laissez-moi essayer de comprendre votre contexte ici:

... l'ordre d'insertion est important ... Trier la carte serait une opération lourde ...

... le résultat de la requête est déjà trié comme je le veux

Maintenant, ce que vous faites actuellement:

Je récupère un ensemble de tuples de la base de données, et le mettre dans une carte ...

Et voici votre code actuel:

public void processData(LinkedHashMap<Key, Value> data) {...}

Ma suggestion est de faire ce qui suit:

  • Utilisez injection de dépendance et injectez du MyTupleRepository dans la méthode de traitement (MyTupleRepository est une interface implémentée par des objets qui récupèrent vos objets Tuple, généralement à partir de DB);
  • en interne à la méthode de traitement, placez les données du référentiel (alias DB, qui renvoie déjà les données ordonnées) dans la collection LinkedHashMap spécifique, car il s'agit de détails internes de l'algorithme de traitement (car cela dépend de la façon dont le les données sont organisées dans la structure de données);
  • Notez que c'est à peu près ce que vous faites déjà, mais dans ce cas, cela se ferait dans le cadre de la méthode de traitement. Votre référentiel est instancié ailleurs (vous avez déjà une classe qui retourne des données, c'est le référentiel dans cet exemple)

Exemple de code

public interface MyTupleRepository {
    Collection<MyTuple> GetAll();
}

//Concrete implementation of data access object, that retrieves 
//your tuples from DB; this data is already ordered by the query
public class DbMyTupleRepository implements MyTupleRepository { }

//Injects some abstraction of repository into the processing method,
//but make it clear that some exception might be thrown if data is not
//arranged in some specific way you need
public void processData(MyTupleRepository tupleRepo) throws DataNotOrderedException {

    LinkedHashMap<Key, Value> data = new LinkedHashMap<Key, Value>();

    //Represents the query to DB, that already returns ordered data
    Collection<MyTuple> myTuples = tupleRepo.GetAll();

    //Optional: this would throw some exception if data is not ordered 
    Validate(myTuples);

    for (MyTupleData t : myTuples) {
        data.put(t.key, t.value);
    }

    //Perform the processing using LinkedHashMap...
    ...
}

Je suppose que cela supprimerait l'avertissement Sonar et spécifierait également dans la signature la disposition spécifique des données requises par la méthode de traitement.

0
Emerson Cardoso

Si vous souhaitez indiquer que la structure de données que vous avez utilisée existe pour une raison, ajoutez un commentaire au-dessus de la signature de la méthode. Si un autre développeur à l'avenir rencontre cette ligne de code et remarque un avertissement d'outil, il peut également remarquer le commentaire et s'abstenir de "résoudre" le problème. S'il n'y a pas de commentaire, rien ne les empêchera de changer la signature.

Supprimer les avertissements est inférieur à commenter à mon avis, car la suppression elle-même n'indique pas la raison pour laquelle l'avertissement a été supprimé. Une combinaison de suppression d'avertissement et de commentaire conviendra également.

0
Kapol