我有几个组件存在不同的版本,具体取决于系统使用的语言(可配置,并且可以在运行时更改)。例如,我有一个用于 Tokenizer
的接口(interface)(“组件”),以及两个用于英语和中文的具体实现,如下所示:
public interface Tokenizer {
List<String> tokenize(String s);
}
public class EnglishTokenizer implements Tokenizer {
List<String> tokenize(String s) { ... };
}
public interface ChineseTokenizer implements Tokenizer {
List<String> tokenize(String s) { ... };
}
现在,在我的代码的许多类中,我需要获得其中一些组件(Tokenizer
、Parser
和许多其他组件)的特定于语言的实现,并且我想知道实现此目标的最优雅方法是什么?我想到了使用以下方法之一:
每个组件(例如
Tokenizer
)都会有一个工厂(单例),给定一个语言enum
,它将返回适当的语言特定实现,像这样(这需要许多工厂):public enum TokenizerFactory { SINGLETON; private Map<Language, Tokenizer> cache; public getTokenizer(Language) { return cache.get(Language); } }
有一个(相当大的)
Language
类,它将用特定语言enum
实例化,并且会有许多不同的方法来获取特定语言成分。然后,在运行时,我可以轻松地在语言之间切换(这是我的目标之一)。像这样:public class Language { public Language(LanguageEnum) {/* load language specific components*/}; public Tokenizer getTokenizer() {/* language specific tokenizer */}; public Parser getParser() {/* language specific parser */}; }
实现我想要做的事情的最合适的方法是什么?我怎样才能改进我的代码?
最佳答案
Spring Framework是一款非常有用的软件,也是我个人的最爱,但还有很多替代品,例如 Google Guice .
使用 Spring,您可以定义两个(三个、十五个……)独立的上下文,每种语言一个,并从适当的上下文中获取所需的组件。它类似于您的第二种方法,但不使用 Language
类。例如:
# English context: english.xml
<bean id="Tokenizer" class="EnglishTokenizer"/>
<bean id="Parser" class="EnglishParser"/>
...
# Your code
ApplicationContext englishContext = ...; // context itself is injected
Parser englishParser = (Parser) englishContext.getBean("Parser");
另一种选择是有一个单一的上下文,但在你的 bean id 前面加上你的语言,例如“English-Tokenizer”和“Chinese-Tokenizer”。
如果您以前从未使用过依赖注入(inject),那么对于可以通过工厂和/或动态类加载实现的结果来说,这听起来似乎工作量太大了:-)但事实并非如此-而且它可以做更多的事情(您可以配置组件的属性/依赖项;您不必担心缓存或维护您自己的单例等...),一旦您开始使用它,您就会想知道没有它您是如何生活的:- )
更新(回答第二条评论中的问题)。
这是一个示例“ComponentLocator”模式。 ComponentLocator
是一个不依赖于 Spring 的单例。它的实例(和实现)由上下文注入(inject)。
public abstract class ComponentLocator {
protected static ComponentLocator myInstance;
protected abstract <T> T locateComponent(Class<T> componentClass, String language);
public static <T> T getComponent(Class<T> componentClass, String language) {
return myInstance.locateComponent(componentClass, language);
}
}
ComponentLocator
的实现假定您上下文中的 bean 被命名为它们的接口(interface)名称,后跟分号和语言(例如“com.mypackage.Parser:English”)。 ComponentLocatorImpl 必须在您的上下文中声明为 bean(bean 名称无关紧要)。
public class ComponentLocatorImpl extends ComponentLocator
implements ApplicationContextAware {
private ApplicationContext myApplicationContext;
public void setApplicationContext(ApplicationContext context) {
myApplicationContext = context;
myInstance = this;
}
@Override
protected <T> T locateComponent(Class<T> componentClass, String language) {
String beanName = componentClass.getName() + ":" + language;
return componentClass.cast(myApplicationContext.getBean(beanName, componentClass));
}
}
在您其他地方的代码中(在 main() 中?)您将加载 ApplicationContext
:
ApplicationContext ctx = new ClasspathXmlApplicationContext("components.xml");
请注意,您实际上不需要在应用程序的其他任何地方直接引用上下文。无论您需要在哪里获取组件,您都可以:
Parser englishParser = ComponentLocator.getComponent(Parser.class, "English");
Parser chineseParser = ComponentLocator.getComponent(Parser.class, "Chinese");
请注意,以上只是一种可能的方法,它假设您几乎只将依赖于语言的类放在上下文中。在您的情况下,这可能是最好的(由于要求所有语言同时可用),否则您将复制所有类(class)(每种语言一次),因此您的 A/B/C 问题可能不适用于此处。
但如果你确实有 A/B/C 依赖,你可以做的是(我假设 A、B、C 是接口(interface),Aimpl、Bimpl、Cimpl 是它们的实现):
<bean id="A" class="Aimpl">
<property name="B" ref="B"/>
</bean>
<bean id="B" class="Bimpl">
<property name="C" ref="C"/>
</bean>
<bean id="C" class="Cimpl">
<property name="tokenizer" ref="Tokenizer:English"/>
</bean>
您的实现需要有 setB()、setC() 和 setTokenizer() 方法。这比构造函数注入(inject)更容易,尽管后者也是可能的。
关于java - 多语言组件/类 [OOP/Patterns/IoC/DI],我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1217143/