我有几个组件,它们存在不同的版本,具体取决于系统使用的语言(可配置,并且可以在运行时更改)。例如,我有Tokenizer
的接口(“组件”),以及英语和中文的两个具体实现,如下所示:
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 */}; }
达到我想要做的最合适的方法是什么?如何改进我的代码?
答案 0 :(得分:3)
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”。
如果您以前从未使用过依赖注入,这可能听起来像是通过工厂和/或动态类加载可以实现的结果太多了:-)但它不是 - 而且它可以做得更多(你可以配置组件的属性/依赖关系;你不必担心缓存或维护你自己的单身......等等,一旦你开始使用它,你会想知道如果没有它你是如何生活的: - )
更新(在第二条评论中回答问题)。
这是一个示例“ComponentLocator”模式。 ComponentLocator
是一个不依赖于Spring的单例。它的实例(和实现)由上下文注入。
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被命名为其接口名称,后跟分号和语言(例如“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");
请注意,上面只是一种可能的方法,它假设您几乎只将语言相关的类放在上下文中。在您的情况下,这可能是最好的(由于要求同时提供所有语言),否则您将复制所有课程(每种语言一次),因此您的A / B / C问题可能不适用于此处。
但如果你确实有A / B / C依赖,你可以做的是(我假设A,B,C是接口,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()方法。这比构造函数注入更容易,但后者也是可能的。
答案 1 :(得分:1)
考虑变化的维度。 “添加新语言”和“添加新组件”等用例。你有多容易做到这一点,你多么容易避免错误。
我不清楚你的地图是如何在第一种情况下填充的,某种注册方案?我是否正确地认为完整性的责任分散在许多班级中。我也总是怀疑单身人士。
在第二种情况下,如果添加新的conmponent,则必须在语言类中添加一个新方法,并确保它有效。添加新语言似乎已本地化到构造函数(可能还有一些其他的实现方法)。
总的来说,我更喜欢第二种方法。
答案 2 :(得分:0)
我同意Spring的回答,IOC将是要走的路。非框架方法是使用AbstractFactory。