4 votes

Comment encapsuler une structure d'objet complexe d'un tiers ?

Motivation

Actuellement, j'utilise le parseur java japa pour créer un arbre syntaxique abstrait (AST) d'un fichier java. Avec cet AST, je fais de la génération de code (par exemple : s'il y a une annotation sur une méthode, créer d'autres fichiers sources, ...).

Problème

Lorsque la génération de mon code devient plus complexe, je dois plonger plus profondément dans la structure de l'AST (par exemple, je dois utiliser les visiteurs pour extraire certaines informations sur les paramètres des méthodes).

Mais je ne sais pas si je veux rester avec japa ou si je changerai la bibliothèque de parser plus tard.

Comme mon générateur de code utilise freemarker (qui n'est pas doué pour le remaniement automatique), je veux que l'interface qu'il utilise pour accéder aux informations de l'AST soit stable, même si je décide de changer l'analyseur java.

Pregunta

Quelle est la meilleure façon d'encapsuler les structures de données complexes de bibliothèques tierces ?

  1. Je pourrais créer mes propres types de données et y copier les parties de l'AST dont j'ai besoin.

  2. Je pourrais créer de nombreuses méthodes d'accès spécialisées qui fonctionnent avec l'AST et créent exactement les informations dont j'ai besoin (par exemple, le type de retour entièrement qualifié d'une méthode sous forme de chaîne, ou le premier paramètre de modèle d'une classe).

  3. Je pourrais créer des classes enveloppes pour les structures de données japa dont j'ai actuellement besoin et intégrer les types japa à l'intérieur, de sorte que je puisse déléguer des requêtes aux types japa et transformer à nouveau les types japa résultants dans mes classes enveloppes.

Quelle solution dois-je prendre ? Existe-t-il d'autres (meilleures) solutions à ce problème ?

3voto

mdma Points 33973

Je vote pour 2 ou 3, avec une préférence pour 3 (voir ci-dessous les raisons de ne pas choisir 1).

Pour (2), vous pourriez commencer par une interface ASTNode

interface ASTNode
{
   List<ASTNode> children();
}

Vous pourriez alors créer des sous-types pour des types spécifiques de nœuds syntaxiques, ou ajouter des collections génériques à l'interface pour récupérer des attributs.

Vous mettez en œuvre des instances de cette interface, par exemple JapaASTNode qui englobe le nœud AST de la bibliothèque. L'inconvénient de cette méthode est que vous finissez par créer un grand nombre de wrappers, et leur gestion peut être délicate, par exemple la gestion du mappage de vos wrappers vers les AST sous-jacents. Certains clients peuvent s'attendre à recevoir le même wrapper pour le même AST, plutôt que de nouveaux wrappers à chaque fois que le même AST est utilisé. Ce problème peut être résolu en utilisant un WeakHashMap, qui garde la trace du wrapper utilisé pour chaque AST, sans retenir l'AST plus longtemps que nécessaire.

Un exemple concret de ce schéma se trouve dans dom4j - Les interfaces sont utilisées pour envelopper les objets DOM de différents analyseurs XML, cachant le reste du système de l'implémentation spécifique de DOM.

L'autre alternative (3) est de ne pas envelopper les objets, mais de fournir des méthodes de navigation qui savent comment trouver les objets liés (par exemple, les ndoes enfants) et extraire des informations, par exemple, obtenir les tokens associés au noeud AST.

Pour ce faire, on dispose d'une interface Navigator qui prend les AST (dans leur forme originale à partir de la bibliothèque) et sait comment les traiter, par ex.

interface Navigator
{
   List<Object> getChildren(Object ast);
   List<Object> getTokens();
}

qui pourrait être mis en œuvre comme suit

class JapaNavigator implements Navigator
{
    List<Object> getChildren(Object ast) {
        JapaAST japaAST = (JapaAST)ast;
        return japaAST.getChildren();
    };
    // for illustration - not the actual japa api
}

Ça a l'air un peu moche avec le moulage, mais n'oubliez pas que c'est interne.

Cela fonctionne bien si le modèle objet des bibliothèques est similaire, ce qui peut raisonnablement être le cas pour les AST, et cela vous évite d'avoir à envelopper absolument tout. L'inconvénient, c'est que vous travaillez avec Object tout le temps en externe. Cependant, il y a peu de danger de l'utiliser de la mauvaise façon - l'implémentation spécifique de Navigator coule ces objets dans leur type AST attendu, et donc les erreurs sont rapidement découvertes (par exemple, passer un objet qui est un token à une méthode qui attend un AST).

Pour un exemple concret, voir jaxen qui applique avec succès xpath sur un certain nombre de modèles d'objets différents, de la même manière que vous voudriez parcourir différents AST.

EDIT :

Bien que (1) fonctionne, vous devrez coder un grand nombre d'itérateurs, de convertisseurs et de divers objets de modèle AST, pour construire votre modèle indépendant de l'analyseur, ce qui finira par représenter une quantité importante de code. (Par exemple, dupliquer tous les nœuds, dupliquer tous les tokens, les visiteurs pour gérer les différents types de nœuds). Ensuite, vous devrez dupliquer la plupart de ces éléments si vous décidez de passer à un autre parseur. Bien sûr, vous pourriez éviter de dupliquer le constructeur pour différents analyseurs en implémentant une interface neutre pour l'analyseur en utilisant (2) ou (3) ci-dessus, et en l'utilisant comme base de votre copie, mais c'est l'équivalent en programmation de courir après votre queue !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X