2009-07-20 52 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 
    } 
} 

现在,管道的每个组件都具有特定语言的内容。例如,为了分析中文文本,我需要一个库,并且为了分析英文文本,我需要另一个不同的库。

此外,还有一些任务可以用一种语言完成,而另一些则无法完成。解决这个问题的一个办法是将每个管道组件抽象化(实现一些常用方法),然后具体实现具体的语言。与组分A来凤县,我有以下几点:

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

此外,由于每个管道组件是非常沉重的,我需要重用他们,我想创造一个工厂,对于缓存了组件的进一步使用,使用使用的语言为重点,像这样的地图(其它组件会以同样的方式工作):

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方法中看到的那样,我首先确定输入的语言,然后基于此,我需要将管道组件更改为所标识的语言。因此,而不是直接调用的组件,也许我应该从厂家得到他们,就像这样:

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组件封装到每种语言的单个类中。我敦促你考虑的一件事是使用Interface继承,而不是Class继承。然后你可以加入一个引擎,为你做runPipeline的过程。这与Builder/Director pattern类似。在此过程中的步骤将是如下:

  1. 获取输入
  2. 使用工厂方法来获取正确的接口(英文/中国)
  3. 通界面到您的发动机
  4. runPipeline并得到结果

关于extends vs implements话题,Allen Holub goes a bit over the top解释了对Interfaces的偏好。


跟进你的意见:

我在这里建造模式的应用的解释是,你有一个Factory,将返回一个PipelineBuilder。在我的设计中的PipelineBuilder是一个包含A,B,& C的设计,但如果您愿意,您可以为每个设计单独建造。然后,该构建器将被分配给您的PipelineEngine,它使用Builder来生成结果。

由于这是使用Factory来提供Builders,所以您对Factory的上述想法仍然完备,充满了其缓存机制。

关于您选择的abstract扩展名,您可以选择给予您的PipelineEngine重物的所有权。但是,如果确实按abstract的方式执行,请注意您声明的共享字段为private,因此您的子类不可用。

+0

感谢您的意见和建议!我已经阅读了关于Builder模式的一些文章,并且如果我正确理解了它,这个想法应该是有一个`PipelineBuilder`,在给定语言的情况下,可以创建组件A,B和C语言特定版本的方法,然后返回一个方法返回“刚建好”的语言特定的“管道”。然后,我会有一个`PipelineEngine`,它将接收一个`Pipeline`并执行`runPipeline`。现在,我的问题是,我将在运行时切换语言/管道,并且每次创建新管道的成本都很高。我如何缓存它们? – 2009-07-21 01:41:07

1

我喜欢基本的设计。如果这些课程足够简单,我可能会考虑将A/B/C工厂合并为一个班级,因为看起来可能会有一些人在这个级别分享行为。尽管如此,我认为这些确实比它们看起来更复杂,这就是为什么这是不可取的。

使用工厂减少部件之间的耦合的基本方法是合理的,伊莫。

0

如果我没有记错的话,你在呼唤一个工厂实际上是依赖注入的一个非常好的形式。您正在选择最能够满足参数需求并返回的对象实例。

如果我说得对,你可能想看看DI平台。他们做你所做的事情(这很简单,对吗?),然后他们增加一些你现在可能不需要的能力,但是你可能会发现会在以后帮助你。

我只是建议你看看现在解决了什么问题。 DI很容易做你自己,你几乎不需要任何其他工具,但他们可能已经发现你还没有考虑过的情况。 Google发现很多很棒的链接。

从我所见到的DI中,很可能你会想要将整个“管道”的创建移动到工厂中,让它为你做链接,然后把你需要解决的问题交给你一个特定的问题,但现在我真的达到了 - 我对DI的了解比我对代码的知识稍微好一点(换句话说,我把大部分内容从我的屁股中抽出来)。

+0

感谢您的意见。 DI的问题是我需要在运行时更改管道(和组件)。例如,我以一个句子作为输入;我对它做了一些分析以检测它的语言;然后我需要获取流水线中特定于语言的组件(我可能需要使Pipeline成为一个接口,并且具有语言特定版本以简化“开关”)。从我读到的DI中,想法是配置外部依赖关系(例如.xml),并将它们“注入”到远处,这使得在运行时切换不可行。 – 2009-07-21 01:50:04