2017-09-07 53 views
6

我有一個庫,用於解析URL並提取一些數據。每個URL有一個類。要知道哪個類應該處理用戶提供的URL,我有下面的代碼。動態調用工廠中的正確實現

public class HostExtractorFactory { 

private HostExtractorFactory() { 
} 

public static HostExtractor getHostExtractor(URL url) 
     throws URLNotSupportedException { 
    String host = url.getHost(); 

    switch (host) { 
    case HostExtractorABC.HOST_NAME: 
     return HostExtractorAbc.getInstance(); 
    case HostExtractorDEF.HOST_NAME: 
     return HostExtractorDef.getInstance(); 
    case HostExtractorGHI.HOST_NAME: 
     return HostExtractorGhi.getInstance(); 
    default: 
     throw new URLNotSupportedException(
       "The url provided does not have a corresponding HostExtractor: [" 
         + host + "]"); 
    } 
} 

}

的問題是用戶請求多個URL被解析,這意味着我的switch語句越來越大。每次有人想出解析器時,我都必須修改我的代碼以包含它。

爲了達到這個目的,我決定創建一個映射並將它展示給它們,以便當它們的類寫入時,它們可以向工廠註冊自己,通過提供主機名和提取器到工廠。下面是實施這個想法的工廠。

public class HostExtractorFactory { 

private static final Map<String, HostExtractor> EXTRACTOR_MAPPING = new HashMap<>(); 

private HostExtractorFactory() { 
} 

public static HostExtractor getHostExtractor(URL url) 
     throws URLNotSupportedException { 
    String host = url.getHost(); 

    if(EXTRACTOR_MAPPING.containsKey(host)) { 
     return EXTRACTOR_MAPPING.get(host); 
    } else { 
     throw new URLNotSupportedException(
       "The url provided does not have a corresponding HostExtractor: [" 
         + host + "]"); 
    } 
} 

public static void register(String hostname, HostExtractor extractor) { 
    if(StringUtils.isBlank(hostname) == false && extractor != null) { 
     EXTRACTOR_MAPPING.put(hostname, extractor); 
    } 
} 

}

而且用戶會用這樣的說法:

public class HostExtractorABC extends HostExtractor { 

public final static String HOST_NAME = "www.abc.com"; 

private static class HostPageExtractorLoader { 
    private static final HostExtractorABC INSTANCE = new HostExtractorABC(); 
} 

private HostExtractorABC() { 
    if (HostPageExtractorLoader.INSTANCE != null) { 
     throw new IllegalStateException("Already instantiated"); 
    } 

    HostExtractorFactory.register(HOST_NAME, this); 
} 

public static HostExtractorABC getInstance() { 
    return HostPageExtractorLoader.INSTANCE; 
} 
... 

}

我拍着我自己回來的時候,我意識到這不會有任何效果:用戶類在我收到URL時沒有加載,只有工廠,這意味着它們的構造函數永遠不會運行,並且地圖總是空的。所以我回到了繪圖板上,但希望得到這個工作的一些想法或另一種方法來擺脫這個煩人的開關語句。

小號

+1

您可以使用**反射**,或者您必須列出所有已知或「想要可用」的類別,例如,一個配置文件...例如'org.reflections'是一個很好的輕量級庫,用於掃描類路徑。 – dedek

+1

在一個簡單的文本文件中列出它們可以自行維護的類名,將工廠構造移動到一個靜態構造函數並在所有條目中使用'Class.forname'(https://stackoverflow.com/q/8100376/13075)在工廠初始化方法中的文件。 – Henrik

+1

或者:引入它們應用於其類的註釋,並在工廠中掃描類路徑。 – Henrik

回答

1

我會建議你瞭解依賴注入(我愛春天實施)。然後,您就可以寫一個接口一樣

public interface HostExtractorHandler { 
    public String getName(); 
    public HostExtractor getInstance(); 
} 

比你的代碼可以「問」爲實現此接口的所有類,然後你就可以建立自己的地圖將在類的初始化階段。

+0

您可以添加一些示例代碼,如何*在「春季」中爲所有實現此接口的類「詢問」...? – dedek

+2

這裏有一個很好的例子:https://dzone.com/articles/load-all-implementors –

2

另一種選擇是使用Service Loader方法。

有你的實施者增加一些像./resources/META-INF/services/your.package.HostExtractor如下:

their.package1.HostExtractorABC 
their.package2.HostExtractorDEF 
their.package3.HostExtractorGHI 
... 

然後在你的代碼,你可以有這樣的:

HostExtractorFactory() { 
    final ServiceLoader<HostExtractor> loader 
      = ServiceLoader.load(your.package.HostExtractor.class); 

    for (final HostExtractor registeredExtractor : loader) { 
     // TODO - Perform pre-processing which is required. 
     // Add to Map? Extract some information and store? Etc. 
    } 
} 
+0

瓦,不知道這樣的事情是在純java中可用! – dedek

+0

如果你通過詢問某個HostExtractor是否處理'host',或者是接口方法,或者簡單地通過在名稱部分中轉換'host'來結合,你的應用只需要知道一個SPI接口。 –

+0

但是,您必須列出'META-INF/services/your.package.HostExtractor'文件中的所有類... – dedek

1

我會用Reflections library定位解析器。他們都出現在HostExtractor類派生,所以使用該庫找到所有亞型:

Reflections reflections = new Reflections("base.package");  
Set<Class<? extends HostExtractor>> extractorTypes = 
    reflections.getSubTypesOf(HostExtractor.class); 

使用效果在你的工廠來創建實例:

for (Class<? extends HostExtractor> c : extractorTypes) { 
    HostExtractor he = c.newInstance(); 
    EXTRACTOR_MAPPING.put(he.getHostName(), he); 
} 

我做了getHostName方法,但它應該是微不足道的添加到HostExtractor基類。