2009-08-01 64 views
1

根据系统使用的语言(可配置,可在运行时更改),我有几个存在不同版本的组件。例如,我有Tokenizer接口(“组件”),并为英国和中国,两个具体的实现,像这样:多语言组件/类[OOP/Patterns/IoC/DI]

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类,塔t将用特定语言enum实例化,并且会有许多不同的方法来获取特定于语言的组件。然后,在运行时,我可以轻松切换语言(这是我的目标之一)。像这样:

    public class Language { 
        public Language(LanguageEnum) {/* load language specific components*/}; 
        public Tokenizer getTokenizer() {/* language specific tokenizer */}; 
        public Parser getParser() {/* language specific parser */}; 
    } 
    

什么是最合适的方式来实现我想要做什么?我如何改进我的代码?

回答

3

使用dependency injection

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”。

如果你之前从未使用过依赖注入,这可能听起来像工作太多,可以通过工厂和/或动态类加载实现: - 但事实并非如此 - 它可以做得更多(你可以配置你的组件的属性/依赖关系;你不必担心缓存或维护你自己的单例等),一旦你开始使用它,你会想知道你是如何没有它的: - )

更新(在第2条评论中回答问题)。

下面是一个示例“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实现假定在上下文豆类作为他们的接口名称,然后分号和语言(例如“com.mypackage.Parser:英文”)命名。 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的接口和的AIMP1,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()方法。这比构造函数注入更容易,尽管后者也是可能的。

+0

谢谢,我已阅读了一些关于DI概念的文章,但我还没有使用它。但是,我对此有一些疑问:i)我想这些bean可以配置为将参数传递给类的对象,对吗? ii)我的“应用程序上下文”(系统使用的语言)可以在运行时更改,并且我想知道是否可以使用DI进行更改; iii)通过缓存,你的意思是说,如果我两次调用englishContext.getBean(“Parser”),第二个将总是返回一个缓存版本? iv)是否可以使用Spring的IoC容器而不是整个框架? – 2009-08-02 00:00:39

1

考虑变化的尺寸。使用例如“添加新语言”和“添加新组件”。你多容易做到这一点,你多容易避免错误。

我不清楚你的地图是如何填充在第一种情况下,某种注册方案?我是否认为完整性的责任分散在许多阶级中。此外,我总是怀疑单身人士。

在第二种情况下,如果您添加新的conmponent,则必须在语言类中添加一个新方法,并确保其正常工作。添加新的语言似乎是本地化的构造函数(大概是一些更深层次的方法)。

总的来说,我更喜欢第二种方法。

+0

感谢您的意见。地图懒加载组件(我省略`getter`方法)。在这种情况下,我使用了Singletons,因为大多数组件非常重(它们使用大量内存),并且我只能加载它们一次(因此我缓存它们)。 – 2009-08-01 22:16:35

0

我同意春季的答案,国际奥委会将是一段路要走。非框架方法将使用AbstractFactory