2009-07-20 7 views
1

Ich habe eine Pipeline-basierte Anwendung, die Text in verschiedenen Sprachen (etwa Englisch und Chinesisch) analysiert. Mein Ziel ist, ein System zu haben, das auf beiden Sprachen in einem transparenten Weg funktionieren kann. HINWEIS: Diese Frage ist lang, weil es viele einfache Code-Snippets hat.Architektur/Entwurf eines Pipeline-basierten Systems. Wie kann ich diesen Code verbessern?

Die Pipeline besteht aus drei Komponenten (lassen sie rufen A, B und C), und ich habe sie in der folgenden Art und Weise erstellt, so dass die Komponenten nicht eng gekoppelt:

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 
    } 
} 

Nun hat jede Komponente der Pipeline etwas, das sprachspezifisch ist. Um beispielsweise chinesischen Text zu analysieren, benötige ich eine Lib, und zum Analysieren von englischem Text brauche ich eine andere Lib.

Darüber hinaus gibt es einige Aufgaben, die in einer Sprache ausgeführt werden können, und auf der anderen nicht ausgeführt werden können. Eine Lösung für dieses Problem besteht darin, jede Pipelinekomponente abstrakt zu machen (um einige gebräuchliche Methoden zu implementieren) und dann eine konkrete sprachspezifische Implementierung zu haben. Als Beispiel für mit der Komponente A, würde ich habe folgende:

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 ... 
} 

Da zusätzlich jede Pipeline-Komponente ist sehr schwer und ich brauche sie wieder zu verwenden, dachte ich an der Schaffung eine Fabrik, das die Komponente-Caches oben für weitere Verwendung, eine Karte, die die Sprache als Schlüssel verwendet, wie so verwendet (die anderen Komponenten auf die gleiche Weise funktionieren würde):

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()); 
    } 
} 

also, meine Frage ist: Was halten Sie von diesem Design ? Wie kann es sein verbessert? Ich brauche die "Transparenz", weil die Sprache dynamisch geändert werden kann, basierend auf dem Text, der analysiert wird. Wie Sie aus der -Methode sehen können, identifiziere ich zuerst die Sprache der Eingabe, und basierend darauf muss ich die Pipeline-Komponenten in die angegebene Sprache ändern. Also, anstatt direkt die Komponenten aufrufen, vielleicht sollte ich sie von der Fabrik erhalten, etwa so:

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); 
} 

Danke bis hierher zum Lesen. Ich schätze jeden Vorschlag, den Sie zu dieser Frage machen können.

Antwort

1

Die Fabrikidee ist gut, ebenso wie die Idee, die A-, B-, & C-Komponenten in einzelne Klassen für jede Sprache zu kapseln. Eine Sache, die ich Ihnen dringend empfehlen würde, ist die Verwendung von Interface Vererbung anstelle von Class Vererbung. Sie könnten dann eine Engine integrieren, die den runPipeline Prozess für Sie erledigen würde. Dies ist ähnlich der Builder/Director pattern. Die Schritte in diesem Prozess sein würde, wie folgt:

  1. Eingang
  2. Verwendung Factory-Methode erhalten, um eine korrekte Schnittstelle (Englisch/Chinesisch)
  3. Pass-Schnittstelle in Ihren Motor
  4. runPipeline und erhalten zur Folge zu bekommen

Auf die extends vs implements Thema, Allen Holub goes a bit over the top, um die Präferenz für Interfaces zu erklären.


Folgen Sie Kommentare auf:

Meine Interpretation der Anwendung des Builder-Muster hier wäre, dass Sie eine Factory haben, die eine PipelineBuilder zurückkehren würde. Die PipelineBuilder in meinem Design ist eine, die A, B, & C umfasst, aber Sie könnten separate Builder für jeden haben, wenn Sie möchten. Dieser Builder wird dann an Ihre PipelineEngine übergeben, die die Builder verwendet, um Ihre Ergebnisse zu generieren.

Da dies eine Fabrik verwendet, um die Builder zur Verfügung zu stellen, bleibt Ihre Idee für eine Fabrik in Takt, voll mit seinem Caching-Mechanismus.

In Bezug auf Ihre Wahl der abstract Erweiterung haben Sie die Wahl, Ihre PipelineEngine Eigentum der schweren Objekte zu geben. Wenn Sie jedoch den Pfad abstract verwenden, beachten Sie, dass die freigegebenen Felder, die Sie deklariert haben, private sind und daher für Ihre Unterklassen nicht verfügbar sind.

+0

Danke für die Kommentare und Vorschläge!Ich habe einige Artikel über das Builder-Muster gelesen. Wenn ich es richtig verstanden habe, wäre die Idee, einen PipelineBuilder zu haben, der bei einer Sprache Methoden zur Verfügung stellt, um sprachspezifische Versionen der Komponenten A, B, & C zu erstellen. und dann eine Methode, um die "gerade gebaute" sprachspezifische "Pipeline" zurückzugeben. Dann hätte ich eine 'PipelineEngine', die eine' Pipeline' erhalten und 'runPipeline' ausführen würde. Nun, mein Problem ist, dass ich Sprachen/Pipelines zur Laufzeit wechseln werde und es sehr teuer ist, jedes Mal eine neue Pipeline zu erstellen. Wie kann ich sie zwischenspeichern? –

+0

Zum Thema Extents versus Implements habe ich diesen Artikel auch gelesen, und obwohl es eine nette Lektüre ist, glaube ich, dass die 'Collections' Beispiele irgendwie den Punkt verfehlen, aber ich bekomme das Problem. In meinem speziellen Fall habe ich jedoch einige schwere Objekte, die unter jeder sprachspezifischen Komponente gemeinsam genutzt werden müssen, und einige gebräuchliche Methoden, die auf ihnen arbeiten, daher die "abstract" -Klasse. –

1

Ich mag das grundlegende Design. Wenn die Klassen einfach genug sind, könnte ich in Betracht ziehen, die A/B/C-Fabriken in einer einzigen Klasse zu konsolidieren, da es scheint, als könnten auf dieser Ebene Verhaltensweisen vorhanden sein. Ich gehe davon aus, dass diese wirklich komplexer sind, als sie erscheinen, und deshalb ist das nicht wünschenswert.

Der grundlegende Ansatz der Verwendung von Fabriken zur Verringerung der Kopplung zwischen Komponenten ist Sound, IMO.

0

Wenn ich mich nicht irre, Was Sie eine Fabrik nennen, ist eigentlich eine sehr schöne Form der Abhängigkeitsinjektion. Sie wählen eine Objektinstanz aus, die die Anforderungen Ihrer Parameter am besten erfüllen und zurückgeben kann.

Wenn ich recht habe, sollten Sie sich DI-Plattformen ansehen. Sie tun, was du getan hast (was ziemlich einfach ist, oder?), Dann fügen sie ein paar weitere Fähigkeiten hinzu, die du jetzt vielleicht nicht brauchst, aber vielleicht findest du später Hilfe.

Ich schlage nur vor, Sie schauen, welche Probleme jetzt gelöst sind. DI ist so einfach zu bedienen, dass du kaum andere Werkzeuge brauchst, aber vielleicht hast du Situationen gefunden, die du noch nicht berücksichtigt hast. Google findet viele groß aussehende Links auf Anhieb.

Von dem, was ich von DI gesehen habe, ist es wahrscheinlich, dass Sie die gesamte Kreation Ihrer "Pipe" in die Fabrik verschieben wollen, die Verknüpfung für Sie machen und Ihnen einfach das liefern, was Sie lösen müssen ein spezifisches Problem, aber jetzt erreiche ich wirklich - mein Wissen über DI ist nur ein bisschen besser als mein Wissen über deinen Code (mit anderen Worten, ich ziehe das meiste aus meinem Hintern heraus).

+0

Danke für die Kommentare. Das Problem mit DI ist, dass ich die Pipeline (und die Komponenten) zur Laufzeit ändern muss. Zum Beispiel nehme ich einen Satz als Eingabe; Ich analysiere es, um seine Sprache zu erkennen. und dann muss ich die sprachspezifischen Komponenten der Pipeline bekommen (wahrscheinlich muss ich Pipeline zu einer Schnittstelle machen und sprachspezifische Versionen davon haben, um den "Switch" zu vereinfachen). Nach dem, was ich von DI gelesen habe, besteht die Idee darin, die Abhängigkeiten extern zu konfigurieren (z. B. .xml) und sie "wegzuwerfen", was es unmöglich macht, zur Laufzeit umzuschalten. –

Verwandte Themen