views:

270

answers:

3

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?

+1  A: 

Consider the dimensions of change. Use cases such as "Add a new language" and "Add a new component". How readily can you do this, how readily do you avoid mistakes.

I'm not clear how your map is populated in the first case, some kind of registration scheme? Am I right in thinking that responsibility for completeness is spread across many classes. Also I'm always suspicous of Singletons.

In the second case if you add a new conmponent then you must add one new method in the language class, and make sure it works. Adding new Languages seems to be localised to the constructor (and presumably some further implmentation methods).

Overall I prefer the second approach.

djna
Thanks for the comments. The map lazy loads the components (I omitted it in the `getter` method). In this case, I used Singletons because most of the components are very heavy (they use lots of memory), and I can only load them once (therefore, I cache them).
JG
+3  A: 

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.

ChssPly76
Thanks, I've read some articles about the concept of DI, but I haven't used it yet. However, I do have some questions about it: i) I suppose those beans can be configured in order to pass arguments to the ctor of the classes, right?; ii) My "application context" (language that the system uses) can be changed at runtime, and I was wondering if it's possible to do so with DI; iii) By caching, you mean that if I call englishContext.getBean("Parser") two times, the second would always return a cached version?; iv) Is it possible to just use Spring's IoC container instead of the whole framework?
JG
1) I'm not sure what you mean by "pass arguments to creator". You can specify properties (or constructor arguments) for the beans themselves in the context.2) If you're going with context-per-language, you'll have a set of ApplicationContext instances and you'll be getting a bean (e.g. Parser) from an appropriate one. Contexts themselves can easily be reloaded at runtime should you need that.3) By default, yes. But if that's not what you want you can change bean scope to always return a new instance.4) Certainly. You can choose only the modules (and JARs) you need.
ChssPly76
Yes, I meant constructor arguments, thanks. I think I definitely must give DI a try. Final questions: Where (in the code) do you typically store the ApplicationContext(s), and invoke the .getBean() methods? Do you have some kind of helper class to handle them ? Also, let's say I have a class `A` that creates class `B` that creates class `C`, and it's class `C` that needs to get a `Tokenizer`. In this case, to use DI, do I need to change the constructors of both `A`,`B`, and `C` to take a `Tokenizer` as argument, and then inject it on `A` in order for it to get to the other classes ?
JG
Doh! ctor=constructor. Don't know what I was thinking :-) I'll update my answer to show a sample patter for dealing with context, comment's no place for code. As far as A/B/C classes go, it all depends how far you want to go with DI - you can certainly inject pretty much everything in your application but that may or may not be ideal in your circumstances. Constructor injection, while possible, is a bit harder then property injection where you just have a setXXX() method. I'll show a sample in my answer as well.
ChssPly76
Excellent detailed answer - thanks
djna
A: 

I agree with the Spring answer, IOC would be the way to go. The non-framework approach would be to use AbstractFactory.

Dave Sims