2009-08-01 58 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