问题描述
我有几个组件存在不同的版本,这取决于系统使用的语言(可配置,并且可以在运行时更改).例如,我有一个用于 Tokenizer
的接口(组件"),以及两个针对英文和中文的具体实现,如下所示:
public interface Tokenizer {列表标记化(字符串 s);}公共类 EnglishTokenizer 实现了 Tokenizer {列表标记化(字符串 s){ ... };}公共接口 ChineseTokenizer 实现了 Tokenizer {列表标记化(字符串 s){ ... };}
现在,在我的代码的许多类中,我需要获得其中一些组件(Tokenizer
、Parser
和许多其他组件)的特定语言实现,并且我想知道实现这一目标的最优雅的方法是什么?我想过使用以下方法之一:
每个组件(例如
Tokenizer
),都会有一个工厂(单例),给定一种语言enum
,将返回适当的语言特定实现,像这样(这将需要许多工厂):public enum TokenizerFactory {单身;私人地图<语言,分词器>缓存;公共 getTokenizer(语言){返回缓存.get(语言);}}
有一个(相当大的)
Language
类,它将用特定语言enum
实例化,并且有许多不同的方法来获取特定语言成分.然后,在运行时,我可以轻松地在语言之间切换(这是我的目标之一).像这样:公共类语言{public Language(LanguageEnum) {/* 加载语言特定组件*/};public Tokenizer getTokenizer() {/* 语言特定的标记器 */};public Parser getParser() {/* 语言特定的解析器 */};}
实现我想要做的事情的最合适的方法是什么?如何改进我的代码?
使用 依赖注入.
Spring 框架是一个非常有用的部分软件和我个人最喜欢的软件,但有很多替代方案,例如 Google Guice.
使用 Spring,您可以定义两个(三个、十五个……)单独的上下文,每种语言一个,并从适当的上下文中获取所需的组件.它类似于您的第二种方法,但不使用 Language
类.例如:
# 英文上下文:english.xml<bean id="Tokenizer" class="EnglishTokenizer"/><bean id="Parser" class="EnglishParser"/>...# 你的代码ApplicationContext englishContext = ...;//上下文本身被注入解析器 englishParser = (Parser) englishContext.getBean("Parser");
另一种选择是有一个单一的上下文,但在你的 bean id 前面加上你的语言,例如English-Tokenizer"和Chinese-Tokenizer".
如果您以前从未使用过依赖注入,对于可以通过工厂和/或动态类加载来实现的结果来说,这听起来工作量太大了:-) 但事实并非如此 - 而且它可以做更多(您可以配置组件的属性/依赖项;您不必担心缓存或维护自己的单例等...)一旦您开始使用它,您会想知道没有它您是如何生活的:-)
更新(回答第二条评论中的问题).
这是一个示例ComponentLocator"模式.ComponentLocator
是一个不依赖 Spring 的单例.它的实例(和实现)由上下文注入.
公共抽象类 ComponentLocator {受保护的静态 ComponentLocator myInstance;受保护的摘要 <T>T locateComponent(Class componentClass, String language);公共静态<T>T getComponent(Class componentClass, String language) {返回 myInstance.locateComponent(componentClass, language);}}
ComponentLocator
的实现假设您上下文中的 bean 被命名为它们的接口名称,后跟分号和语言(例如com.mypackage.Parser:English").ComponentLocatorImpl 必须在您的上下文中声明为 bean(bean 名称无关紧要).
公共类 ComponentLocatorImpl 扩展 ComponentLocator实现 ApplicationContextAware {私有 ApplicationContext myApplicationContext;公共无效setApplicationContext(ApplicationContext上下文){myApplicationContext = 上下文;myInstance = 这个;}@覆盖受保护的 <T>T locateComponent(Class componentClass, String language) {String beanName = componentClass.getName() + ":" + 语言;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, "中文");
请注意,以上只是一种可能的方法,它假设您几乎只是将依赖于语言的类放在上下文中.在您的情况下,这可能是最好的(由于要求同时提供所有语言),否则您将复制所有课程(每种语言一次),因此您的 A/B/C 问题可能不适用于此处.
但是,如果您确实有 A/B/C 依赖性,那么您可以做的是(我假设 A、B、C 是接口,而 Aimpl、Bimpl、Cimpl 是它们的实现):
<属性名称="B" ref="B"/></bean><bean id="B" class="Bimpl"><属性名称="C" ref="C"/></bean><bean id="C" class="Cimpl"><property name="tokenizer" ref="Tokenizer:English"/></bean>
您的实现需要有 setB()、setC() 和 setTokenizer() 方法.这比构造函数注入更容易,尽管后者也是可能的.
I have several components for which there exists different versions, depending on the language used by the system (configurable, and can be changed at runtime). For example, I have an interface ("component") for Tokenizer
, and two concrete implementations for english and chinese, like so:
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) { ... };
}
Now, in many classes of my code, I need to get a language specific implementation of some of those components (Tokenizer
, Parser
, and many others), and I was wondering what's the most elegant way to achieve this? I thought of using one of the following approaches:
Each component (such as
Tokenizer
), would have a factory (singleton) that, given a languageenum
, would return the appropriate language specific implementation, like so (this would require many factories):Have a (quite large)
Language
class, that would be instantiated with a specific languageenum
, and would have many different methods to get the language specific components. Then, at runtime, I could easily switch between languages (which is one of my goals). Like so:
What is the most appropriate way to achieve what I'm trying to do? How can I improve my code?
Use dependency injection.
Spring Framework is an extremely useful piece of software and my personal favorite but there are many alternatives such as Google Guice.
Using Spring, you would define two (three, fifteen, ...) separate contexts, one per language, and obtain needed component from appropriate context. It's similar to your second approach but without using Language
class. For example:
# 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");
Another alternative is to have a single context but prefix your bean ids with your language, e.g. "English-Tokenizer" and "Chinese-Tokenizer".
If you've never used dependency injection before, this may sound like too much work for a result that can be achieved via factory and / or dynamic class loading :-) But it's not - and it can do so much more (you can configure properties / dependencies of your components; you don't have to worry about caching or maintaining your own singletons, etc...) that once you start using it you'll wonder how you've ever lived without it :-)
Update (answers questions in 2nd comment).
Here's a sample "ComponentLocator" pattern. ComponentLocator
is a singleton that has no dependencies on Spring. Its instance (and implementation) is injected by the context.
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);
}
}
Implementation of ComponentLocator
assumes beans in your context are named as their interface names followed by semicolon and language (e.g. "com.mypackage.Parser:English"). ComponentLocatorImpl must be declared as bean in your context (bean name doesn't matter).
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));
}
}
In your code elsewhere (in main()?) you're going to load ApplicationContext
:
ApplicationContext ctx = new ClasspathXmlApplicationContext("components.xml");
Note that you don't actually need to refer to the context directly anywhere else in the application. Wherever you need to get your components you just do:
Parser englishParser = ComponentLocator.getComponent(Parser.class, "English");
Parser chineseParser = ComponentLocator.getComponent(Parser.class, "Chinese");
Note that the above is just one possible approach and it assumes that you're pretty much only putting your language-dependent classes in the context. In your case that's probably the best (due to requirement of having all languages available simultaneously) otherwise you'd be replicating all your classes (once per language), so your A/B/C question is probably not applicable here.
But if you do have A/B/C dependency what you can do is (I'm assuming A, B, C are interfaces and Aimpl, Bimpl, Cimpl are their implementations):
<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>
Your implementations would need to have setB(), setC() and setTokenizer() methods. This is easier then constructor injection, though latter is also possible.
这篇关于多语言组件/类 [OOP/Patterns/IoC/DI]的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!