多语言组件/类[OOP / Patterns / IoC / DI]

时间:2009-08-01 18:13:25

标签: java design-patterns oop architecture dependency-injection

我有几个组件,它们存在不同的版本,具体取决于系统使用的语言(可配置,并且可以在运行时更改)。例如,我有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) { ... };
}

现在,在我的代码的许多类中,我需要获得某些组件的语言特定实现(TokenizerParser和许多其他组件),我想知道什么是最多的优雅的方式实现这一目标?我想过使用以下方法之一:

  • 每个组件(例如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 */};
    }
    

达到我想要做的最合适的方法是什么?如何改进我的代码?

3 个答案:

答案 0 :(得分: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”。

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

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

这是一个示例“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