我有一个基于管道的应用程序,它分析不同语言(比如,英语和中文)的文本。我的目标是拥有一个可以以透明的方式在两种语言中工作的系统。注意:这个问题很长,因为它有许多简单的代码片段。
管道由三个组件组成(我们将它们称为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);
}
谢谢你阅读到这里。我非常感谢你就这个问题提出的每一个建议。
3条答案
按热度按时间kyks70gy1#
工厂的想法是好的,如果可行的话,封装A、B& C组件合并到每种语言的单个类中。我强烈建议您考虑的一件事是使用
Interface
继承而不是Class
继承。然后您可以合并一个引擎来为您执行runPipeline
过程。这与Builder/Director pattern类似。这一进程的步骤如下:1.获取输入
1.使用工厂方法得到正确的界面(英语/中文)
1.将接口传递到引擎
1.运行管道并获取结果
在
extends
与implements
主题中,Allen Holub goes a bit over the top用于解释Interfaces
的首选项。跟进您的评论:
我对Builder模式应用的解释是,您有一个
Factory
,它将返回一个PipelineBuilder
。我设计中的PipelineBuilder
包含A、B和C,但是如果您愿意,可以为每个模式使用单独的构建器。然后,将此构建器提供给您的PipelineEngine
,PipelineEngine
使用Builder
生成您的结果。由于这是利用工厂来提供构建器,所以您上面关于工厂的想法仍然是巧妙的,充满了它的缓存机制。
关于
abstract
扩展的选择,您可以选择将重对象的所有权交给PipelineEngine
,但是,如果您选择abstract
,请注意,您声明的共享字段是private
,因此对您的子类不可用。nvbavucw2#
我喜欢基本的设计。如果类足够简单,我可能会考虑将A/B/C工厂合并到一个类中,因为看起来在那个级别上可能会有一些行为共享。不过,我假设这些工厂实际上比它们看起来的要复杂,这就是为什么不希望这样做的原因。
使用工厂来减少组件之间耦合的基本方法是合理的,imo。
64jmpszr3#
如果我没弄错的话,你所调用的工厂实际上是一种非常好的依赖注入形式,你选择了一个最能满足你的参数需求的对象示例,并返回它。
如果我是对的,你可能会想看看DI平台。它们做你做过的事情(这很简单,对吗?)然后它们会添加一些你现在可能不需要的能力,但你可能会发现以后会对你有帮助。
我只是建议你看看现在解决了什么问题。DI是如此容易自己做,你几乎不需要任何其他工具,但他们可能会发现你还没有考虑到的情况。Google发现了许多伟大的前瞻性链接马上 bat 。
从我对DI的了解来看,您很可能希望将整个“Pipe”创建过程移到工厂中,让它为您进行链接,并只提供您解决特定问题所需的东西,但现在我真的要达到--我对DI的了解只比我对代码的了解多一点(换句话说,我从屁股里拿出了大部分内容)。