2009-07-20 8 views
1

J'ai une application basée sur pipeline qui analyse le texte en différentes langues (disons, anglais et chinois). Mon objectif est d'avoir un système qui peut fonctionner dans les deux langues, de manière transparente. NOTE: Cette question est longue car elle contient de nombreux extraits de code simples.Architecture/Conception d'un système basé sur un pipeline. Comment améliorer ce code?

Le pipeline se compose de trois composants (permet de les appeler A, B et C), et je les ai créés de la manière suivante, de sorte que les composants ne sont pas étroitement couplés:

public class Pipeline { 
    private A componentA; 
    private B componentB; 
    private C componentC; 

    // I really just need the language attribute of Locale, 
    // but I use it because it's useful to load language specific ResourceBundles. 
    public Pipeline(Locale locale) { 
     componentA = new A(); 
     componentB = new B(); 
     componentC = new C(); 
    } 

    public Output runPipeline(Input) { 
     Language lang = LanguageIdentifier.identify(Input); 
     // 
     ResultOfA resultA = componentA.doSomething(Input); 
     ResultOfB resultB = componentB.doSomethingElse(resultA); // uses result of A 
     return componentC.doFinal(resultA, resultB); // uses result of A and B 
    } 
} 

Maintenant, chaque composant du pipeline a quelque chose à l'intérieur qui est spécifique à la langue. Par exemple, pour analyser le texte chinois, j'ai besoin d'une lib, et pour l'analyse du texte anglais, j'ai besoin d'une autre lib.

En outre, certaines tâches peuvent être effectuées dans une langue et ne peuvent pas être effectuées de l'autre. Une solution à ce problème consiste à rendre chaque composant de pipeline abstrait (pour implémenter des méthodes communes), puis avoir une implémentation concrète spécifique au langage. Illustrant avec le composant A, je donne les résultats suivants:

public abstract class A { 
    private CommonClass x; // common to all languages 
    private AnotherCommonClass y; // common to all languages 

    abstract SomeTemporaryResult getTemp(input); // language specific 
    abstract AnotherTemporaryResult getAnotherTemp(input); // language specific 

    public ResultOfA doSomething(input) { 
      // template method 
      SomeTemporaryResult t = getTemp(input); // language specific 
      AnotherTemporaryResult tt = getAnotherTemp(input); // language specific 
      return ResultOfA(t, tt, x.get(), y.get()); 
    } 
} 

public class EnglishA extends A { 
    private EnglishSpecificClass something; 
    // implementation of the abstract methods ... 
} 

En outre, étant donné que chaque composant de pipeline est très lourd et j'ai besoin de les réutiliser, je pensais que la création d'une usine qui met en cache le composant pour une utilisation ultérieure, en utilisant une carte qui utilise la langue comme la clé, comme si (les autres composants fonctionnerait de la même manière):

public Enum AFactory { 
    SINGLETON; 

    private Map<String, A> cache; // this map will only have one or two keys, is there anything more efficient that I can use, instead of HashMap ? 

    public A getA(Locale locale) { 
     // lookup by locale.language, and insert if it doesn't exist, et cetera 
     return cache.get(locale.getLanguage()); 
    } 
} 

alors, ma question est: que pensez-vous de cette conception ? Comment peut-il être amélioré? J'ai besoin de la "transparence" parce que le langage peut être changé dynamiquement, sur la base du texte qui est analysé. Comme vous pouvez le voir à partir de la méthode runPipeline, j'identifie d'abord la langue de l'Input, puis, sur cette base, je dois changer les composants du pipeline dans la langue identifiée. Ainsi, au lieu d'invoquer les composants directement, peut-être que je devrais les faire de l'usine, comme ceci:

public Output runPipeline(Input) { 
    Language lang = LanguageIdentifier.identify(Input); 
    ResultOfA resultA = AFactory.getA(lang).doSomething(Input); 
    ResultOfB resultB = BFactory.getB(lang).doSomethingElse(resultA); 
    return CFactory.getC(lang).doFinal(resultA, resultB); 
} 

Merci d'avoir lu jusqu'ici. J'apprécie énormément chaque suggestion que vous pouvez faire sur cette question.

Répondre

1

L'idée d'usine est bonne, de même que l'idée, si possible, d'encapsuler les composants A, B, & C en classes uniques pour chaque langue. Une chose que je vous exhorte à considérer est d'utiliser l'héritage Interface au lieu de l'héritage Class. Vous pourriez alors incorporer un moteur qui ferait le processus runPipeline pour vous. Ceci est similaire à Builder/Director pattern. Les étapes de ce processus seraient les suivantes:

  1. obtenir des commentaires
  2. utiliser la méthode de l'usine pour obtenir une interface correcte (anglais/chinois)
  3. interface passe dans votre moteur
  4. runPipeline et obtenir le résultat

Sur le extends par rapport au implements sujet, Allen Holub goes a bit over the top pour expliquer la préférence pour Interfaces.


Suivi de vous commentaires:

Mon interprétation de l'application du modèle Builder serait ici que vous avez un Factory qui renverrait une PipelineBuilder. Le PipelineBuilder dans ma conception est celui qui entoure A, B, & C, mais vous pourriez avoir des constructeurs distincts pour chacun si vous le souhaitez. Ce générateur est ensuite donné à votre PipelineEngine qui utilise le Builder pour générer vos résultats. Comme cela fait usage d'une usine pour fournir les constructeurs, votre idée ci-dessus pour une usine reste intacte, rempli de son mécanisme de mise en cache.

En ce qui concerne le choix de l'extension abstract, vous avez le choix de donner à vos PipelineEngine la propriété des objets lourds. Cependant, si vous adoptez la méthode abstract, notez que les champs partagés que vous avez déclarés sont private et ne seraient donc pas disponibles pour vos sous-classes.

+0

Merci pour vos commentaires et suggestions!J'ai lu quelques articles sur le pattern Builder et si j'ai bien compris, l'idée serait d'avoir un PipelineBuilder qui, avec un langage, aurait des méthodes pour créer des versions spécifiques au langage des composants A, B et C, puis une méthode pour renvoyer le "Pipeline" spécifique au langage "juste construit". Ensuite, j'aurais un 'PipelineEngine' qui recevrait un' Pipeline' et exécuterait 'runPipeline'. Maintenant, mon problème est que je vais changer de langages/pipelines en cours d'exécution, et il est très coûteux de créer un nouveau pipeline à chaque fois. Comment puis-je les mettre en cache? –

+0

En ce qui concerne le problème des extensions par rapport aux implémentations, j'ai également lu cet article, et bien que ce soit une bonne lecture, je pense que les exemples 'Collections' manquent quelque peu le point, mais j'ai le problème. Cependant, dans mon cas particulier, j'ai des objets lourds qui doivent être partagés entre chaque composant spécifique de la langue, et quelques méthodes communes qui fonctionnent sur eux, d'où la classe '' abstract''. –

1

J'aime la conception de base. Si les classes sont assez simples, je pourrais envisager de consolider les usines A/B/C dans une seule classe, car il semble qu'il pourrait y avoir un certain partage de comportement à ce niveau. Je suppose que ce sont vraiment plus complexes qu'ils n'apparaissent, et c'est pourquoi cela n'est pas souhaitable.

L'approche de base de l'utilisation des usines pour réduire le couplage entre les composants est saine, imo.

0

Si je ne me trompe pas, ce que vous appelez une usine est en fait une très belle forme d'injection de dépendance. Vous sélectionnez une instance d'objet qui est la plus à même de répondre aux besoins de vos paramètres et de la renvoyer.

Si j'ai raison à ce sujet, vous voudrez peut-être regarder dans les plates-formes DI. Ils font ce que vous avez fait (ce qui est assez simple, n'est-ce pas?), Puis ils ajoutent quelques capacités supplémentaires dont vous n'avez peut-être pas besoin maintenant, mais que vous trouverez peut-être plus tard.

Je vous suggère simplement de regarder quels problèmes sont résolus maintenant. DI est si facile à faire vous-même que vous n'avez pratiquement pas besoin d'autres outils, mais ils pourraient avoir trouvé des situations que vous n'avez pas encore considérées. Google trouve beaucoup de superbes liens dès le départ. D'après ce que j'ai vu de DI, il est probable que vous voudrez déplacer toute la création de votre "Pipe" dans l'usine, en lui faisant faire le lien pour vous et en vous donnant juste ce que vous avez besoin de résoudre un problème spécifique, mais maintenant j'atteins vraiment - ma connaissance de DI est juste un peu meilleure que ma connaissance de votre code (en d'autres termes, je tire la plupart de ce hors de mes fesses).

+0

Merci pour les commentaires. Le problème avec DI est que j'ai besoin que le pipeline (et les composants) soit changé au moment de l'exécution. Par exemple, je prends une phrase en entrée; Je fais une analyse dessus pour détecter son langage; et puis j'ai besoin d'obtenir les composants spécifiques au langage du pipeline (j'ai probablement besoin de faire de Pipeline une interface, et d'avoir des versions spécifiques au langage pour simplifier le "switch"). D'après ce que j'ai lu de DI, l'idée est de configurer les dépendances de manière externe (par exemple, .xml), et de les "injecter", ce qui rend impossible de passer à l'exécution. –

Questions connexes