多种语言组件/类[OOP / Patterns / IoC / DI] [英] Multiple language components/classes [OOP/Patterns/IoC/DI]

查看:191
本文介绍了多种语言组件/类[OOP / Patterns / IoC / DI]的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有几个组件,根据系统使用的语言(可配置,可以在运行时更改),存在不同的版本。例如,我有一个用于 Tokenizer 的界面(component),以及两个具体的英文和中文实现,如:

  public interface Tokenizer {
列表< String> tokenize(String s);
}

public class EnglishTokenizer实现Tokenizer {
列表< String> tokenize(String s){...};
}

public interface ChineseTokenizer实现Tokenizer {
列表< String> tokenize(String s){...};
}

现在,在我的代码的许多类中,我需要一个特定的语言执行某些组件( Tokenizer Parser 等等),我想知道什么是最优雅实现这个方法?我想到使用以下方法之一:




  • 每个组件(例如 Tokenizer )会有一个工厂(单例),给出一个语言枚举,将返回适当的语言特定实现,像这样(这将需要许多工厂):


      public enum TokenizerFactory {
    SINGLETON;
    private Map< Language,Tokenizer>缓存;
    public getTokenizer(Language){
    return cache.get(Language);
    }
    }



  • 有一个(相当大的)语言类,将使用特定语言枚举实例化,并且会有许多不同获取语​​言特定组件的方法。然后,在运行时,我可以轻松地切换语言(这是我的目标之一)。像这样:


      public class语言{
    public Language(LanguageEnum){/加载语言特定的组件* /};
    public Tokenizer getTokenizer(){/ * language specific tokenizer * /};
    public Parser getParser(){/ *语言特定解析器* /};
    }





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

解决方案

使用依赖注入



Spring Framework 是一个非常有用的软件和个人最喜爱的软件,但有很多备选方案,如 Google Guice



使用Spring,您将定义两个(三,十五,...)个独立的上下文,每个语言一个,从适当的上下文获取所需的组件。它类似于你的第二种方法,但不使用语言类。例如:

 #English context:english.xml 

< bean id =Tokenizer类= EnglishTokenizer/>
< bean id =Parserclass =EnglishParser/>

...

#你的代码

ApplicationContext englishContext = ...; //上下文本身被注入
Parser englishParser =(Parser)englishContext.getBean(Parser);

另一个选择是拥有一个上下文,但是用您的语言为您的bean ids提供前提。 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 
实现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) ; 
解析器chineseParser = ComponentLocator.getComponent(Parser.class,Chinese);

请注意,上述只是一种可能的方法,它假定您几乎只将您的上下文中依赖语言的类。在您的情况下,这可能是最好的(由于要求同时提供所有语言)否则您将复制所有课程(每种语言一次),因此您的A / B / C问题可能不适用于此。但是如果你有A / B / C依赖,你可以做的是(我假设A,B,C是接口和Aimpl,Bimpl,Cimpl是他们的实现):

 < bean id =Aclass =Aimpl> 
< property name =Bref =B/>
< / bean>

< bean id =Bclass =Bimpl>
< property name =Cref =C/>
< / bean>

< bean id =Cclass =Cimpl>
< property name =tokenizerref =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 language enum, would return the appropriate language specific implementation, like so (this would require many factories):

    public enum TokenizerFactory {
        SINGLETON;
        private Map<Language, Tokenizer> cache;
        public getTokenizer(Language) {
            return cache.get(Language); 
        }
    }
    

  • Have a (quite large) Language class, that would be instantiated with a specific language enum, 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:

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

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]的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆