2009-07-20 5 views
1

私はの異なるテキストを分析するパイプラインベースのアプリケーションを持っています(例えば、英語と中国語)私のゴールは、の透明なという形で両方の言語で動作するシステムを持つことです。 注::この質問には、簡単なコードスニペットがたくさんあるため、長いです。パイプラインベースのシステムのアーキテクチャ/設計このコードを改善するには?

コンポーネントが緊密に結合されないように、パイプラインは、三つの成分(A、B、およびCにそれらを呼び出すことができます)で構成されている、と私は、次の方法でそれらを作成しました:

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

現在、パイプラインのすべてのコンポーネントには、言語固有のものがあります。たとえば、中国語のテキストを分析するには、1つのlibが必要であり、英語のテキストを分析するために、別の別のlibが必要です。

さらに、1つの言語で実行できるタスクもあり、もう一方のタスクでは実行できないタスクもあります。この問題に対する1つの解決策は、すべてのパイプラインコンポーネントを抽象的な(いくつかの一般的なメソッドを実装するために)抽象化し、具体的な言語固有の実装を行うことです。また

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

、各パイプラインコンポーネントは非常に重く、私はそれらを再利用する必要があるので、私はコンポーネントをキャッシュする工場を作成するので考えた:成分Aを例示する、私は次の必要があるだろうさらに使用するには、(他のコンポーネントは、同じように動作します)のようなので、キーなどの言語を使用してマップを使用して:

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

をだから、私の質問です:あなたはこのデザインをどう思いますか?どうすればが改善されますか?私は、分析されているテキストに基づいて言語を動的に変更できるので、「透明性」が必要です。 runPipelineメソッドからわかるように、まずInputの言語を特定し、それに基づいてパイプラインコンポーネントを特定の言語に変更する必要があります。だから、代わりに直接コンポーネントを呼び出すと、多分私はそうのように、工場からそれらを取得する必要があります。

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

ここまでお読みいただきありがとうございました。私はこの質問にあなたができるすべての提案を非常に感謝します。

答えて

1

可能であれば、A、B、&のCコンポーネントを各言語の単一のクラスにカプセル化するという考え方は良いことです。あなたが検討してほしいことの1つは、Class継承の代わりにInterfaceの継承を使用することです。その後、runPipelineプロセスを実行するエンジンを組み込むことができます。これはBuilder/Director patternに似ています。このプロセスの手順は以下のようになります:

  1. は(中国語/英語)正しいインターフェイスを取得するための入力
  2. 使用ファクトリメソッドを取得
  3. パスインタフェースあなたのエンジンへ
  4. runPipelineと
  5. を結果取得

extendsimplementsトピックでは、の優先度を説明するAllen Holub goes a bit over the topトピックについて説明します。


あなたのコメントをフォローアップ:ここビルダーパターンのアプリケーションの

私の解釈はあなたがPipelineBuilderを返しますFactoryを持っているということでしょう。私のデザインのPipelineBuilderは、A、B、& Cをエンコードするものですが、好きな場合は、それぞれ別々のビルダーを用意することができます。この建築業者はBuilderを使用して結果を生成するPipelineEngineに与えられます。

これはファクトリを使用してビルダーを提供するため、ファクトリのための上記の考え方はそのまま残り、キャッシングメカニズムが充実しています。

abstractの拡張子については、PipelineEngineに重いオブジェクトの所有権を与えるかどうかを選択する必要があります。しかし、abstractの方法を行った場合、宣言した共有フィールドはprivateであり、したがって、あなたのサブクラスでは利用できないことに注意してください。

+0

コメントと提案をありがとう!ビルダーのパターンに関する記事を読んだことがありますが、それが正しく理解されていれば、言語を指定すると、コンポーネントA、B、Cの言語固有のバージョンを作成するメソッドを持つ 'PipelineBuilder'を作成し、そして、 "ちょうど造られた"言語特有のパイプラインを返す方法。次に、私は 'パイプライン'を受け取り、 'runPipeline'を実行する'パイプラインエンジン 'を持っています。さて、私の問題は、実行時に言語/パイプラインを切り替えることです。毎回新しいパイプラインを作成するのは非常にコストがかかります。どのようにキャッシュできますか? –

+0

拡張と実装の問題については、その記事も読んでいますが、読んだほうがいいですが、コレクションの例が何らかの理由で見逃していると思いますが、問題が発生します。しかし、私の特別なケースでは、すべての言語固有のコンポーネント間で共有する必要のある重いオブジェクトと、それらを操作する一般的なメソッドがあるので、 '' abstract''クラスがあります。 –

1

私は基本的なデザインが好きです。クラスが十分にシンプルであれば、A/B/Cファクトリを単一のクラスにまとめることも考えられます。そのレベルでの振る舞いにはいくつかの共有があるようです。私はこれらが実際よりも複雑であると仮定していますが、それが望ましくない理由です。

コンポーネント間の結合を減らすためにファクトリを使用する基本的なアプローチは健全です。

0

私が間違っていないとすれば、あなたが工場と呼んでいるものは、実際には依存性注入のとても良い形態です。パラメータのニーズを最もよく満たすことができるオブジェクトインスタンスを選択して返します。

私が正しいと思うのであれば、DIプラットフォームを調べることができます。彼らはあなたがしたことをします(これはかなりシンプルですが、今は必要ないかもしれない能力をいくつか追加しますが、後で助けてくれるかもしれません)。

私はちょうどあなたが今解決された問題を見てみることを提案しています。 DIはあなた自身が他のツールをほとんど必要としないのでとても簡単ですが、あなたがまだ考えていない状況を見つけたかもしれません。 Googleは、すぐにバットから多くのすばらしいリンクを見つけます。

私はDIを見てきたので、あなたの "パイプ"の作成全体を工場に移して、あなたのためのリンクを行い、解決する必要があるものを手渡すことが必要になるでしょう特定の問題ですが、今は本当に到達しています - 私のDIに関する知識は、自分のコードに関する私の知識よりもちょっと優れています(言い換えれば、私は大部分を私のお尻から引き出しています)。

+0

コメントありがとうございます。 DIの問題は、実行時にパイプライン(およびコンポーネント)を変更する必要があることです。たとえば、私は入力として文を取る。私はその言語を検出するためにいくつかの分析を行います。パイプラインの言語固有のコンポーネントを取得する必要があります(おそらく、Pipelineをインターフェイスにし、「スイッチ」を簡略化するために言語固有のバージョンを用意する必要があります)。私がDIを読んだところでは、依存関係を外部(例えば、.xml)に設定し、実行時に切り替えることができないように、それらを「注入」させることが考えられます。 –

関連する問題