program - switch java ejemplo menu
Invocar dinámicamente la implementación correcta en una fábrica (3)
Tengo una biblioteca que analiza las URL y extrae algunos datos. Hay una clase por URL. Para saber qué clase debe manejar la URL proporcionada por el usuario, tengo el código a continuación.
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 + "]");
}
}
}
El problema es que los usuarios están solicitando más URL para ser analizados, lo que significa que mi declaración de cambio está creciendo. Cada vez que alguien aparece con un analizador, tengo que modificar mi código para incluirlo.
Para finalizar, he decidido crear un mapa y exponerlo, de modo que cuando se escriba su clase, puedan registrarse ellos mismos en la fábrica, proporcionando el nombre de host y el extractor a la fábrica. A continuación está la fábrica con esta idea implementada.
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);
}
}
}
Y el usuario lo usaría de esa manera:
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;
}
...
}
Me estaba dando palmadas cuando me di cuenta de que esto nunca funcionaría: las clases de usuario no se cargan cuando recibo la URL, solo la fábrica, lo que significa que su constructor nunca se ejecuta, y el mapa siempre está vacío. Así que estoy de vuelta a la mesa de dibujo, pero me gustaría algunas ideas sobre cómo hacer que esto funcione o sobre otro enfoque para deshacerse de esta molesta declaración de cambio.
S
Otra opción es usar el enfoque de cargador de servicio .
Con sus implementadores, agregue algo como lo siguiente en ./resources/META-INF/services/your.package.HostExtractor
:
their.package1.HostExtractorABC
their.package2.HostExtractorDEF
their.package3.HostExtractorGHI
...
Luego, en tu código, puedes tener algo como:
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.
}
}
Te aconsejaría que aprendieras sobre la inyección de dependencia (me encanta la implementación de primavera ). Entonces podrá escribir una interfaz como
public interface HostExtractorHandler {
public String getName();
public HostExtractor getInstance();
}
Luego de que tu código pueda "preguntar" por todas las clases que implementen esta interfaz, entonces podrás construir tu mapa en la fase de inicialización de tu clase.
Usaría la biblioteca Reflections para ubicar los analizadores. Todos parecen derivar de la clase HostExtractor
, por lo tanto, use la biblioteca para localizar todos los subtipos:
Reflections reflections = new Reflections("base.package");
Set<Class<? extends HostExtractor>> extractorTypes =
reflections.getSubTypesOf(HostExtractor.class);
Use los resultados para crear instancias en su fábrica:
for (Class<? extends HostExtractor> c : extractorTypes) {
HostExtractor he = c.newInstance();
EXTRACTOR_MAPPING.put(he.getHostName(), he);
}
getHostName
método getHostName
, pero debería ser trivial agregarlo a la clase base de HostExtractor
.