Le problème
Envisager une mise en œuvre d'un graphique, SampleGraph<N>
. Envisager une mise en œuvre des nœuds du graphe, Node extends N
, prépondérants correctement hashCode
et equals
de refléter l' égalité logique entre deux nœuds.
Maintenant, disons que nous voulons ajouter une propriété p à un nœud. Une telle propriété est liée à des instances logiques d'un noeud, par exemple pour Node n1, n2
, n1.equals(n2)
implique p ( n1
) = p ( n2
)
Si j'ajoute simplement la propriété comme un champ de la Node
classe, ce qui est arrivé à moi:
- Je définis de
Node n1, n2
telle sorte que ,n1.equals(n2)
maisn1 != n2
- Ajouter
n1
etn2
à un graphique:n1
lors de l' insertion du noeud logique, etn2
lors du référencement pour le noeud lors de l' insertion des bords. Le graphique stocke les deux cas. - Plus tard, je récupère le nœud du graphe (
n1
est retourné) et définissez la propriété p sur elle une certaine valeur. Plus tard, je traverse toutes les arêtes du graphe, et récupérer le nœud d'un d'entre eux (n2
est retourné). La propriété p est pas définie, ce qui provoque une erreur logique dans mon modèle.
Pour résumer, le comportement actuel :
graph.addNode(n1) // n1 is added
graph.addEdge(n2,nOther) // graph stores n2
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n2 is returned
La question
Tous les énoncés suivants semblent raisonnables pour moi. Aucun d'entre eux me convainc entièrement sur les autres, donc je suis à la recherche de lignes directrices sur les pratiques fondées sur les canons de génie logiciel.
S1 - La mise en œuvre du graphique est faible. Lors de l' ajout d' un nœud, le graphique doit toujours vérifier en interne si elle a une instance du même noeud ( equals
Equivaut à true) mémorisé. Si oui, par exemple doit toujours être la seule référence utilisée par le graphique.
graph.addNode(n1) // n1 is added
graph.addEdge(n2,nOther) // graph internally checks that n2.equals(n1), doesn't store n2
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n1 is returned
S2 - En supposant que le se comporte de graphe comme dans S1 est une erreur. Le programmeur doit veiller à ce que toujours la même instance d'un nœud est passé au graphique.
graph.addNode(n1) // n1 is added
graph.addEdge(n1,nOther) // the programmer uses n1 every time he refers to the node
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n1 is returned
S3 - La propriété est pas mise en œuvre de la bonne façon. Il devrait être une information qui est externe à la classe Node
. Une collection, comme un HashMap<N, Property>
, fonctionnerait très bien, le traitement de différentes instances comme le même objet sur la base hashCode
.
HashMap<N, Property> properties;
graph.addNode(n1) // n1 is added
graph.addEdge(n2,nOther) // graph stores n2
graph.queryForNode({query}) // n1 is returned
graph.queryForEdge({query}).sourceNode() // n2 is returned
// get the property. Difference in instances does not matter
properties.get(n1)
properties.get(n2) //same property is returned
S4 - Comme S3, mais nous pourrions cacher à l'intérieur de la mise en œuvre Node
, de cette façon:
class Node {
private static HashMap<N, Property> properties;
public Property getProperty() {
return properties.get(this);
}
}
Modifier : extraits de code ajouté pour le comportement actuel et les solutions provisoires suivantes Stephen C de réponse . Pour clarifier, l'ensemble de l' exemple vient d'utiliser une véritable structure de données de graphique à partir d' un projet Java open source.
Pour moi, il revient de choisir entre des API avec abstraction forte ou faible.
Si vous choisissez l' abstraction forte, l'API se cacher le fait que les
Node
objets ont l' identité, et je les canoniser quand ils sont ajoutés à laSimpleGraph
.Si vous choisissez l' abstraction faible, l'API supposerait que les
Node
objets ont l' identité, et il appartiendrait à l'appelant de les canoniser avant de les ajouter à laSimpleGraph
.
Les deux approches conduisent à différents contrats API et nécessitent différentes stratégies de mise en œuvre. Le choix est susceptible d'avoir des répercussions sur la performance ... si cela est important.
Ensuite, il y a des détails plus fins de la conception de l'API qui peuvent ou peuvent ne pas correspondre à votre cas d'utilisation spécifique pour les graphiques.
Le point est que vous devez faire le choix.
(Ce bit est comme décider d'utiliser les collections List
interface et son modèle propre, par rapport à la mise en œuvre de votre propre structure de données de liste chaînée afin que vous puissiez efficacement « épissage » 2 listes ensemble. Approche soit pourrait être correcte, en fonction des besoins de votre application .)
Notez que vous habituellement pouvez faire un choix, bien que le choix peut être difficile. Par exemple, si vous utilisez une API conçue par quelqu'un d' autre:
- Vous pouvez choisir d'utiliser tel quel. (Suck it up!)
- Vous pouvez choisir d' essayer d' influencer la conception. (Bonne chance!)
- Vous pouvez choisir de passer à une API différente; soit un autre fournisseur.
- Vous pouvez choisir de bifurquer l'API et l'ajuster à vos propres besoins (ou les préférences si c'est ce que cela est sur le point)
- Vous pouvez choisir de concevoir et mettre en œuvre votre propre API à partir de zéro.
Et si vous vraiment n'avez pas le choix, cette question est sans objet. Il suffit d' utiliser l'API.
Si cela est une API open source , alors vous n'avez probablement pas le choix d'obtenir les concepteurs de changer. API importantes remises en état ont tendance à créer beaucoup de travail pour d' autres personnes; à savoir les nombreux autres projets qui dépendent de l'API. Une équipe designer API responsable / conception prend en compte. Ou bien ils trouvent qu'ils perdent la pertinence parce que leurs API obtiennent la réputation d'être instable.
Alors ... si vous visez à influencer une conception API open source existant ... « car vous pensez qu'ils le font de manière incorrecte (pour une définition incorrecte) ... vous êtes probablement mieux « bifurquer » l'API et faire face aux conséquences.
Et enfin, si vous cherchez des conseils « meilleures pratiques », sachez qu'il n'y a pas de meilleures pratiques . Et ce n'est pas seulement une question philosophique. C'est pourquoi vous aurez vissé si vous allez demander / la recherche de conseils « meilleures pratiques », puis suivez.
En note: avez-vous déjà demandé pourquoi les bibliothèques de classes Java standard et Android ne proposent aucune API graphique à usage général ou mises en œuvre? Et pourquoi ils ont si longtemps à apparaître dans les bibliothèques 3ème partie (version Goyave 20.0)?
La réponse est qu'il n'y a pas de consensus sur ce qu'une telle API devrait être. Il y a trop de cas d'usage conflictuel et ensembles d'exigences.