Многоязычные компоненты / классы [ООП / Шаблоны / IoC / DI] - PullRequest
1 голос
/ 01 августа 2009

У меня есть несколько компонентов, для которых существуют разные версии, в зависимости от языка, используемого системой (настраивается и может быть изменен во время выполнения). Например, у меня есть интерфейс («компонент») для 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) { ... };
}

Теперь, во многих классах моего кода мне нужно получить реализацию некоторых из этих компонентов (Tokenizer, Parser и многие другие) для конкретного языка, и мне было интересно, какой самый элегантный способ достижения этот? Я подумал об использовании одного из следующих подходов:

  • Каждый компонент (такой как 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 ]

3 голосов
/ 02 августа 2009

Использовать внедрение зависимости .

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");

Другая альтернатива - иметь один контекст, но с префиксом идентификаторов вашего бина с вашим языком, например, "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 голос
/ 01 августа 2009

Рассмотрим размеры изменения. Варианты использования, такие как «Добавить новый язык» и «Добавить новый компонент». Насколько легко вы можете это сделать, как легко вы избегаете ошибок.

Мне не понятно, как ваша карта заселена в первом случае, какая-то схема регистрации? Правильно ли я считаю, что ответственность за полноту распределена по многим классам. Также я всегда с подозрением отношусь к синглетонам.

Во втором случае, если вы добавляете новый компонент, вы должны добавить один новый метод в языковой класс и убедиться, что он работает Добавление новых языков, по-видимому, локализовано в конструкторе (и, возможно, некоторые дополнительные методы имплементации).

В целом я предпочитаю второй подход.

0 голосов
/ 12 января 2010

Я согласен с ответом Spring, МОК был бы подходящим вариантом.Некаркасный подход будет использовать AbstractFactory .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...