org.gvsig.tools.locator
Patrón Locator para librerías
El patrón Locator ::::::::::::::::::::::::::::::::: El *Locator* es un objeto que proporciona referencias a componentes, servicios u objetos en general de una aplicación. .. figure:: images/service-locator-img :align: center Este patrón permite separar cómo se gestiona o instancia un objeto, de la propia implementación del objeto. Además, esta independencia también aplica a los clientes del objeto, que no tienen que conocer cómo deben acceder o instanciar una referencia a un objeto, delegando esta tarea en el *Locator*. De esta forma, podemos sustituir la opción de tener un *Singleton* por cada objeto de la aplicación o sistema, por un *Locator*. Aunque para la implementación de un *Locator* se suele emplear el patrón *Singleton*. Dependiendo del tamaño y estructura de la aplicación, se suele tener un único *Locator* para toda la aplicación, uno por cada sistema o librería, etc. Hay que destacar que el patrón *Locator* **no** es un patrón de creación, como los de tipo *Factory*, sino que sólo se centra en la obtención de referencias. La creación de las instancias que devolverá el *Locator* queda fuera del patrón. Aplicación del Locator en gvSIG ::::::::::::::::::::::::::::::::: Locator --------- Para gvSIG, el patrón del Locator se aplicará con una implementación basada en el uso de los puntos de extensión. Para los módulos o librerías, se usará además un mecanismo de inicialización que permita registrar las implementaciones que aporte en su Locator. Para la implementación del *Locator* se ha creado un interfaz, que define los métodos de uso general, una implementación abstracta, basada en el uso de los puntos de extensión, y una clase base: .. figure:: images/locator_classes.png :align: center El interfaz del *Locator* se han definido métodos que permiten registrar clases, o factorías, así cómo obtener instancias de los mismos. Hay que tener en cuenta que el comportamiento del Locator no es como factoría, por lo que se espera que el método *get* devuelva cada vez la misma instancia de objeto. Es decir, actúa como un contenedor de referencias a objetos que funcionan como *Singleton*. La implementación abstracta la aporta la clase *AbstractLocator*, y la implentación básica *BaseLocator*. La idea es que cada librería implemente su *Locator* extendiendo esta clase, aportando métodos que registren y obtengan referencias a sus tipos de objetos directamente. Más adelante, se muestra un ejemplo de implementación que aporta este tipo de métodos. Además, cada implementación de *Locator* deberá hacerse empleando el patrón *Singleton*, para facilitar el acceso al *Locator*. El uso del Locator nos aporta algunas ventajas adicionales, como son: - Las implementaciones no incorporan nada en lo que respecta a cómo se crean o se obtienen, lo que mejora su extensibilidad y la creación de tests unitarios. Además, se facilita la programación con interfaces y la definición de APIs. - Soporte a implementaciones alternativas: como por ejemplo para Java SE y Java ME, o cuando haya una implementación básica, y el usuario pueda instalar una implementación extendida. - Soporte de múltiples implementaciones: si existen distintas implementaciones para un mismo interfaz o componente, que se usan dependiendo de distintas opciones, como acciones del usuario, tipos de datos, etc. Ejemplo de uso --------------- A continuación se plantea un ejemplo, basado en la librería *libCompat*. Esta librería plantea el caso de implementaciones alternativas para Java SE y Java ME, por lo que requerirá una estructura de proyectos que permita organizar la librería en tres componentes de instalación: libCompat: Será la librería de API. Contendrá los APIs de la librería, en forma de interfaces de las distintas utilidades que aporta (StringUtils, FileUtils, etc.), así como un *CompatLocator* que dé acceso a las implementaciones correspondientes de dichas utilidades. También puede contener algunas implementaciones Base o Abstractas, que se empleen de forma común en las implementaciones por entorno. libCompatSE: Incluirá las implementaciones para Java SE. libCompatME: Proporciona las implementaciones para Java ME. A continuación se muestran ejemplos de código, centrándonos en el uso de una de las utilidades de *libCompat*: *StringUtils*. Primero, en *libCompat* se incluirá la definición del interfaz de *StringUtils*: .. code-block:: java package org.gvsig.compat.lang; public interface StringUtils { String[] split(String input, String regex); ... } En *libCompatSE*, tendremos una implementación: .. code-block:: java package org.gvsig.compat.se.lang; import org.gvsig.compat.lang.StringUtils; public class StandardStringUtils implements StringUtils { public String[] split(String input, String regex) { return input == null ? null : input.split(regex); } ... } En *libCompatME*, la implementación alternativa: .. code-block:: java package org.gvsig.compat.me.lang; import java.util.regex.Pattern; import org.gvsig.compat.lang.StringUtils; public class MobileStringUtils implements StringUtils { public String[] split(String input, String regex) { return input == null ? null : Pattern.compile(regex).split(input, 0); } ... } En *libCompat* tenemos la implementación del *Locator* de la librería, que quedaría como sigue: .. code-block:: java package org.gvsig.compat; import org.gvsig.compat.lang.StringUtils; public class CompatLocator extends BaseLocator { /** The name of the StringUtils reference. */ public static final String STRINGUTILS_NAME = "StringUtils"; /** Unique instance. */ private static final CompatLocator instance = new CompatLocator(); /** * Return the singleton instance. * @return the singleton instance */ public static CompatLocator getInstance() { return instance; } /** * Return a reference to StringUtils. * @return a reference to StringUtils * @throws LocatorException * if there is no access to the class or the class cannot be * instantiated * @see Locator#get(String) */ public static StringUtils getStringUtils() throws LocatorException { return (StringUtils) getInstance().get(STRINGUTILS_NAME); } /** * Registers the Class implementing the StringUtils interface. * @param clazz * implementing the StringUtils interface */ public static void registerStringUtils(Class clazz) { getInstance() .register(STRINGUTILS_NAME, STRINGUTILS_DESCRIPTION, clazz); } } En esta implementación de Locator, se han añadido métodos estáticos que facilitan el uso del mismo, y manejan referencias de los tipos de la librería en concreto, como el *StringUtils*. Así, por ejemplo, los clientes no tienen porqué usar el método *get(name)* genérico del *Locator*, sino que pueden usar un método *getStringUtils()* directamente. Una clase cliente que quiera usar, por ejemplo, el *StringUtils*, podrá hacerlo de la siguiente forma: .. code-block:: java public class SampleClient { private StringUtils stringUtils = CompatLocator.getStringUtils(); public void doSomething() { String text = "one,two,three"; String[] words = stringUtils.split(text, ","); ... } } Sólo quedaría por ver cómo se registran las implementaciones a usar en el Locator. Para ello emplearemos el mecanismo de inicialización de librerias del paquete `org.gvsig.tools.library`__ . __ org-gvsig-tools-library/ *libCompatSE* y *libCompatME* tendrán cada una su *Library* que registre la implementación correspondiente de StringUtils. SECompatLibrary: .. code-block:: java package org.gvsig.compat.se; import org.gvsig.compat.CompatLocator; /** * Initialization of the libCompat library, Java Standard Edition * implementation. * @author Cèsar Ordiñana */ public class SECompatLibrary extends BaseLibrary { protected void doInitialize() { super.initialize(); CompatLocator.registerStringUtils(StandardStringUtils.class); } protected void doPostInitialize() { } } MECompatLibrary: .. code-block:: java package org.gvsig.compat.me; import org.gvsig.compat.CompatLocator; /** * Initialization of the libCompat library, Java Mobile Edition implementation. * @author Cèsar Ordiñana */ public class MECompatLibrary extends BaseLibrary { protected void doInitialize() { super.initialize(); CompatLocator.registerStringUtils(MobileStringUtils.class); } protected void doPostInitialize() { } } La librería de API *libCompat*, tiene un Library que comprueba si se ha registrado alguna de las implementaciones, lo que permite validar rápidamente si ha habido algun problema de configuración y no se ha registrado ninguna implementación para *StringUtils*: CompatLibrary: .. code-block:: java package org.gvsig.compat; import org.gvsig.compat.lang.StringUtils; /** * Initialization of the libCompat library. * @author Cèsar Ordiñana */ public class CompatLibrary extends BaseLibrary { protected void doInitialize() { } protected void doPostInitialize() { super.postInitialize(); // Validate there is any implementation registered. StringUtils stringUtils = CompatLocator.getStringUtils(); if (stringUtils == null) { throw new ReferenceNotRegisteredException(CompatLocator.STRINGUTILS_NAME, CompatLocator.getInstance()); } } } Nota ----- Como norma general, al emplear el mecanismo de inicialización de librerías de libTools deberemos seguir un par de normas básica para no tener problemas de orden y dependencias en la inicialización: - El registro de implementaciones en los Locator se hará siempre en el método *doInitialize()*. - El acceso a referencias a través de algún Locator en un *Library*, se hará siempre en el método *doPostInitialize()*. Con esto nos aseguraremos que, siempre que se acceda a una referencia a través de un Locator, se habrá registrado la implementación correspondiente. Tests Unitarios ----------------- Dado que con el uso de los Locator estamos reforzando el uso de interfaces, y la separación de implementaciones alternativas u opcionales, podemos aprovechar esto a la hora de realizar los tests unitarios. La forma de aprovecharlo consiste en crear los tests unitarios que comprueben el funcionamiento correcto a nivel de API, dentro del proyecto que incluya la definición de los interfaces. Dichos tests pueden crearse como clases abstractas, que definan métodos para crear los objetos que implementan los interfaces a probar. Así, cada implementación particular podrá crear sus tests unitarios, simplemente heredando de los tests del API, e implementando el método de creación de sus clases propias. Con esta estructura de tests unitarios podemos definir los tests unitarios a nivel de API, y lanzarlos de forma sencilla para cada implementación. Basándonos en el ejemplo anterior, la distribución de tests unitarios quedaría de la siguiente forma en las tres librerías del ejemplo: *libCompat:* .. code-block:: java public abstract class StringUtilsTestAbstract extends TestCase { private StringUtils utils; protected void setUp() throws Exception { super.setUp(); utils = createUtils(); } protected abstract StringUtils createUtils(); public void testReplaceAll() { String testString = "En un lugar de la Mancha"; String resultString = "En_un_lugar_de_la_Mancha"; String regex = " "; String replacement = "_"; assertEquals(resultString, utils.replaceAll(testString, regex, replacement)); } } *libCompatSE:* .. code-block:: java public class StandardStringUtilsTest extends StringUtilsTestAbstract { protected StringUtils createUtils() { return new StandardStringUtils(); } } *libCompatME:* .. code-block:: java public class MobileStringUtilsTest extends StringUtilsTestAbstract { protected StringUtils createUtils() { return new MobileStringUtils(); } } Notas adicionales -------------------- - Las excepciones del Locator y del Library son de tipo *RuntimeException* por lo que, aunque declaradas en su API, no obligan a ser capturadas mediante un *try ... catch*. A nivel de aplicación, si que habrá que hacer un tratamiento de estas excepciones, para mostrar al usuario algún tipo de información sobre el error producido. No se han definido excepciones normales, ya que son errores graves y no se puedan tratar desde código. Así no obligamos a capturarlas en cada lugar dónde se use un *Locator* o *Library*, y se pueden tratar en un nivel superior.