2017-09-07 20 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基类。