java 架构/设计一个基于流水线的系统,如何改进这段代码?

lp0sw83n  于 2023-01-24  发布在  Java
关注(0)|答案(3)|浏览(129)

我有一个基于管道的应用程序,它分析不同语言(比如,英语和中文)的文本。我的目标是拥有一个可以以透明的方式在两种语言中工作的系统。注意:这个问题很长,因为它有许多简单的代码片段。
管道由三个组件组成(我们将它们称为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 ... 
}

此外,由于每个管道组件都非常繁重,而且我需要重用它们,因此我考虑创建一个工厂来缓存组件以备将来使用,使用一个以语言为关键字的Map,如下所示(其他组件将以相同的方式工作):

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

谢谢你阅读到这里。我非常感谢你就这个问题提出的每一个建议。

kyks70gy

kyks70gy1#

工厂的想法是好的,如果可行的话,封装A、B& C组件合并到每种语言的单个类中。我强烈建议您考虑的一件事是使用Interface继承而不是Class继承。然后您可以合并一个引擎来为您执行runPipeline过程。这与Builder/Director pattern类似。这一进程的步骤如下:
1.获取输入
1.使用工厂方法得到正确的界面(英语/中文)
1.将接口传递到引擎
1.运行管道并获取结果
extendsimplements主题中,Allen Holub goes a bit over the top用于解释Interfaces的首选项。
跟进您的评论:
我对Builder模式应用的解释是,您有一个Factory,它将返回一个PipelineBuilder。我设计中的PipelineBuilder包含A、B和C,但是如果您愿意,可以为每个模式使用单独的构建器。然后,将此构建器提供给您的PipelineEnginePipelineEngine使用Builder生成您的结果。
由于这是利用工厂来提供构建器,所以您上面关于工厂的想法仍然是巧妙的,充满了它的缓存机制。
关于abstract扩展的选择,您可以选择将重对象的所有权交给PipelineEngine,但是,如果您选择abstract,请注意,您声明的共享字段是private,因此对您的子类不可用。

nvbavucw

nvbavucw2#

我喜欢基本的设计。如果类足够简单,我可能会考虑将A/B/C工厂合并到一个类中,因为看起来在那个级别上可能会有一些行为共享。不过,我假设这些工厂实际上比它们看起来的要复杂,这就是为什么不希望这样做的原因。
使用工厂来减少组件之间耦合的基本方法是合理的,imo。

64jmpszr

64jmpszr3#

如果我没弄错的话,你所调用的工厂实际上是一种非常好的依赖注入形式,你选择了一个最能满足你的参数需求的对象示例,并返回它。
如果我是对的,你可能会想看看DI平台。它们做你做过的事情(这很简单,对吗?)然后它们会添加一些你现在可能不需要的能力,但你可能会发现以后会对你有帮助。
我只是建议你看看现在解决了什么问题。DI是如此容易自己做,你几乎不需要任何其他工具,但他们可能会发现你还没有考虑到的情况。Google发现了许多伟大的前瞻性链接马上 bat 。
从我对DI的了解来看,您很可能希望将整个“Pipe”创建过程移到工厂中,让它为您进行链接,并只提供您解决特定问题所需的东西,但现在我真的要达到--我对DI的了解只比我对代码的了解多一点(换句话说,我从屁股里拿出了大部分内容)。

相关问题