我有一个基于管道的应用程序,用于分析不同的语言(例如,英语和中文)中的文本。我的目标是建立一个可以在透明方式下使用这两种语言的系统。 注意:这个问题很长,因为它有很多简单的代码段。
管道由三个组件组成(我们称之为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
}
}
现在,管道的每个组件都有一些特定于语言的内容。例如,为了分析中文文本,我需要一个lib,为了分析英文文本,我需要另一个不同的lib。
此外,有些任务可以用一种语言完成,而另一种语言则无法完成。这个问题的一个解决方案是使每个管道组件都抽象(实现一些常用方法),然后具体具体的语言实现。以组件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
方法可以看出,我首先确定了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);
}
感谢您阅读此内容。我非常感谢你对这个问题提出的每一个建议。
答案 0 :(得分:1)
我喜欢基本设计。如果这些类很简单,我可能会考虑将A / B / C工厂整合到一个类中,因为在该级别上可能会有一些共享行为。我假设这些实际上比它们看起来更复杂,这就是为什么这是不可取的。
使用工厂来减少组件之间耦合的基本方法是合理的,imo。
答案 1 :(得分:1)
工厂的想法是好的,如果可行的话,将封装A,B和&amp;的想法也是如此。 C组件为每种语言的单个类。我要求您考虑的一件事是使用Interface
继承而不是Class
继承。然后,您可以合并一个可以为您执行runPipeline
过程的引擎。这类似于Builder/Director pattern。这个过程的步骤如下:
在extends
vs implements
主题Allen Holub goes a bit over the top上解释Interfaces
的偏好。
跟进你的评论:
我在这里对Builder模式的应用的解释是你有Factory
将返回PipelineBuilder
。我设计中的PipelineBuilder
是一个包含A,B和A的组合。 C,但如果你愿意,你可以为每个人建立单独的构建器。然后,此构建器将发送给PipelineEngine
,Builder
使用abstract
生成结果。
由于这会使用工厂来提供构建器,因此上面对工厂的想法仍然存在,充满了其缓存机制。
关于您选择的PipelineEngine
扩展程序,您可以选择对重物提供abstract
所有权。但是,如果您采用private
方式,请注意您声明的共享字段为{{1}},因此您的子类无法使用。
答案 2 :(得分:0)
如果我没有弄错的话,你所谓的工厂实际上是一种非常好的依赖注入形式。您正在选择最能满足参数需求的对象实例并将其返回。
如果我是对的,你可能想要研究DI平台。他们做你做的事情(这很简单,对吗?)然后他们增加了一些你现在可能不需要的能力,但你可能会发现以后会帮助你。
我只是建议你看看现在解决了什么问题。 DI很容易自己做,你几乎不需要任何其他工具,但他们可能已经找到了你尚未考虑的情况。 Google发现很多很棒的链接。
从我所看到的DI中,你很可能想把你的“管道”的整个创作移到工厂里,让它为你做链接,然后把你需要的东西递给你具体问题,但现在我真正达到了 - 我对DI的了解比我对你的代码的了解要好一些(换句话说,我将大部分内容从我的屁股中拉出来)。