繁体   English   中英

基于管道的系统的架构/设计。 如何改进这段代码?

[英]Architecture/Design of a pipeline-based system. How to improve this code?

我有一个基于管道的应用程序,可以分析不同语言(比如英语和中文)的文本。 我的目标是拥有一个可以以透明方式使用两种语言的系统。 注意:这个问题很长,因为它有很多简单的代码片段。

管道由三个组件组成(我们称它们为 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);
}

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

我喜欢基本的设计。 如果这些类足够简单,我可能会考虑将 A/B/C 工厂合并到一个 class 中,因为在该级别上似乎可以共享一些行为。 不过,我假设这些实际上比它们看起来更复杂,这就是为什么这是不可取的。

imo,使用工厂来减少组件之间耦合的基本方法是合理的。

工厂的想法很好,如果可行的话,将 A、B 和 C 组件封装到每种语言的单个类中。 我敦促您考虑的一件事是使用Interface inheritance 而不是Class inheritance。然后您可以合并一个引擎来为您执行runPipeline过程。 这类似于Builder/Director 模式 此过程中的步骤如下:

  1. 获取输入
  2. 使用工厂方法获得正确的界面(英文/中文)
  3. 将接口传递给你的引擎
  4. 运行管道并获得结果

extends vs implements主题上, Allen Holub 有点过头来解释对Interfaces的偏好。


跟进你的评论:

我在这里对 Builder 模式应用的解释是,您有一个返回PipelineBuilderFactory 在我的设计中, PipelineBuilder是包含 A、B 和 C 的一个,但如果您愿意,您可以为每个构建器使用单独的构建器。 然后将该构建器提供给您的PipelineEngine ,它使用该Builder生成您的结果。

由于这使用工厂来提供构建器,因此您上面关于工厂的想法仍然完好无损,充满了它的缓存机制。

关于您选择的abstract扩展,您确实可以选择让您的PipelineEngine拥有重对象的所有权。 但是,如果您以abstract方式执行 go,请注意您声明的共享字段是private的,因此您的子类将无法使用。

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

如果我是对的,您可能想研究 DI 平台。 他们做你所做的(这很简单,对吧?)然后他们添加了一些你现在可能不需要但你以后可能会发现对你有帮助的能力。

我只是建议你看看现在解决了什么问题。 DI 非常容易自己完成,您几乎不需要任何其他工具,但他们可能已经发现了您尚未考虑的情况。 谷歌立即找到许多漂亮的链接。

从我对 DI 的了解来看,您可能希望将“管道”的整个创建移动到工厂中,让它为您进行链接,并只为您提供解决特定问题所需的东西,但现在我真的达到了——我对 DI 的了解只比我对你的代码的了解好一点(换句话说,我把大部分内容都从我的屁股里拿出来了)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM