Dynamically invoke the correct implementation in the factory

I have a library that parses urls and extracts some data. There is one class for each URL. To find out which class should handle the URL provided by the user, I have the code below.

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 + "]");
    }
}

}

The problem is that users request more URLs for analysis, which means my switch statement is growing. Every time someone comes up with a parser, I have to change my code to enable it.

To finish this, I decided to create a map and expose it for them, so that when they write their class, they can register in the factory by specifying the host name and extractor in the factory. Below is a factory with this idea.

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

}

And the user will use it this way:

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-, factory, , , . , , , , , , switch.

S

+6
3

( spring). ,

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

"" , , .

+1

- 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.
    }
}
+2

. , , HostExtractor, :

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

factory:

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

getHostName, , HostExtractor.

+1
source

All Articles