Contexto de uso
La capa de presentación necesita mecanismos para manejar los distintos tipos de peticiones que requerirán procesos distintos según este tipo. Algunas peticiones son simplemente redirigidas al componente adecuado mientra que otras deben ser modificadas, validadas o descomprimidas antes de ser procesadas.
Problema
Se requiere pre-proceso y post-proceso de la petición (request) y respuesta (response) de un cliente Web.
Cuando una petición llega a una aplicación Web frecuentemente debe pasar varios tests de entrada anteriores al procesamiento principal. Por ejemplo:
- ¿El cliente está autentificado?
- ¿El cliente tiene una sesión válida?
- ¿La IP del cliente viene de una red confiable?
- ¿El path de la petición viola alguna restricción?
- ¿Qué encoding usa el cliente para enviar los datos?
- ¿El tipo de navegador del cliente está soportado?
Algunos de estos tests resultan en una respuesta afirmativa o negativa que determina si el procesamiento de la petición continúa. Otros tests manipulan los datos de entrada para obtenerlos de una forma adecuada para el procesamiento.
La solución clásica pasa por una serie de chequeos condicionales donde cualquier chequeo fallido aborta el procesamiento de la petición: Una serie anidada de sentencias if/else son una estrategia estándar pero esta solución conduce a fragilidad en el código y al estilo de programación copy-paste porque el flujo del filtrado y la acción de los filtros están compilados dentro de la aplicación.
La clave es resolver este problema de una forma flexible teniendo un mecanismo que permita añadir y quitar componentes de procesamiento donde cada componente es una acción de filtrado.
Solución
Crear filtros conectables para procesar servicios comunes de forma estándar sin requerir cambios la núcleo del código del procesamiento de la petición. Los filtros interceptantes toman de la entrada las peticiones (requests) y dan como salida las respuestas (responses) permitiendo pre-procesamiento y post-procesamiento. Se añadirán y quitarán sin requerir cambios al código existente.
Nuestro procesamiento principal será complementado con una variedad de servicios comunes como seguridad, logging, depuración, etc. Estos filtros son componentes independientes del código de la aplicación y se añaden o quitan de forma declarativa. Por ejemplo, usando un fichero de configuración de despliegue (deployment configuration file) podemos especificar una cadena de filtros. Este mismo fichero puede incluir el mapeo de URLs espcíficas a esta cadena de filtros. Cuando un cliente requiere un recurso mapeado mediante esa URL los filtros en cadena son procesados en orden ants de que sea invocado el recurso objetivo de la petición.

Diagrama de Clases del Patrón Filtro Interceptante

Diagrama de Secuencia del Patrón Filtro Interceptante
Estregias de Implementación (se desarrollarán posteriormente en varios posts):
- Estrategia de Filtrado Personalizada
- Estrategia de Filtrado Estándar
- Estrategia de Filtrado Base
- Estrategia de Filtrado Plantilla
Consecuencias del Uso
- Centralizamos lógica con manejadores débilmente acoplados: Los filtros proporcionan la forma de realizar el procesamiento de múltiples peticiones tal y como hace un controlador y son la forma más adecuada para procesar peticiones y respuestas para proveer el manejo final al recurso destino de la petición. Además permiten combinarse para así seleccionar los servicios que se quieren tener.
- Mejoramos la reutilización: Los filtros nos proporcionan una forma clara de particionar la aplicación al mismo tiempo que son reutilizables. Estos interceptores conectables son añadidos o quitados de forma transparente para el código existente y, debido a su interfaz estándar, pueden trabajar en cualquier combinación y ser reutilizados para distintas aplicaciones.
- Configuración Flexible y Declarativa: Podemos combinar muchos servicios variando permutaciones en la configuración sin tener que recompilar el código base.
- Compartir la Información es Ineficiente: Compartir información entre distintos filtros puede ser ineficiente ya que, por definición, cada filtro es débilmente acoplado. Si se comparten grandes cantidades de información entre filtros entonces este planteamiento puede ser costoso en rendimiento.
Tags: Uncategorized
Un diseño modular bueno oculta detalles internos de su implementación entre módulos separando claramente su interfaz de su implementación. Este concepto, conocido como encapsulación, es uno de los fundamentales en el diseño de software.
La encapsulación es importante por varias razones, entre ellas el desacoplamiento, que permite construir, probar, optimizar, usar, comprender y mantener los módulos que componen el sistema de forma individual. Además permite que se puedan desarrollar módulos en paralelo con la consecuente reducción de tiempo en el desarrollo.
La encapsulación también favorece la reutilización puesto que al no depender un módulo del resto esto facilita que el mismo pueda usarse en diferentes contextos.
El lenguaje de programación Java tiene muchas maneras de llevar a cabo la encapsulación. Una de ellas es el control de acceso (access control) que determina la accesibilidad de clases, interfaces y mimbros. La accesibilidad de una entidad está determinada por la localización donde es declarada y, si existen en dicha declaración, por los modificadores de acceso: private, protected y public.
La directriz general a seguir es que se debe hacer cada clase o miembro tan inaccesible como sea posible, es decir, se deberá usar le nivel de acceso más bajo compatible con la funcionalidad que se desea dar a la clase o miembro.
Para clases e interfaces no anidadas hay solamente dos posibles niveles de acceso: package-private y public. Si declaramos una clase o interfaz como public tendrá acceso público. Si no añadimos este modificador de acceso en su declaración, tendrá acceso package-private. Siempre que sea posible preferiremos hacer una clase package-private de manera que si no tiene acceso público no tenemos que mantener compatibilidad con usos externos, solamente con las clases del mismo paquete.
Una clase o interfaz package-private que es utilizada solamente dentro de una clase debería implementarse anidada dentro de la clase en la que se usa. Esto reduce la accesibilidad y al mismo tiempo evita clases públicas innecesarias.
Para miembros de clase (variables, campos y clases e interfaces anidadas) existen cuatro posibles niveles de acceso:
- private: El miembro es accesible solo dentro de la clase donde está declarada y no en clases anidadas de la misma.
- package-private: El miembro es accesible desde cualquier clase del paquete en que está declarado (acceso por defecto).
- protected: El miembro es accesible desde las sublcases de la clase en que está declarada (con restricciones) y desde cualquier otra clase del paquete en que está declarada.
- public: El miembro es accesible desde cualquier sitio.
Una regla para métodos que solapan métodos de superclases es que no se permite que tengan un nivel de acceso menor que los métodos de la superclases. Esto es necesario para asegurar que una instacia de la sublcase se puede usar en cualquier lugar en la que se podría usar una instancia de la superclase. Normalmente el compilador generará un error si no se cumple esta regla al compilar la subclase. Un caso especial de esto es una clase que implementa una interfaz: Todos los métodos de la clase que están también presentes en la interfaz deben declararse públicos ya que todos los métodos de una interfaz son implícitamente públicos.
Raramente una clase pública tiene campos miembro públicos (y lo contrario para los métodos). Hay una excepción a esta regla y es cuando se permite que las clases tengan constantes definidas a través de public static final. Normalmente, por convención de código, estos campos se codifican con letras mayúsculas y palabras separadas por guión bajo (_). En esta excepción es muy importante que estos campos contengan valores primitivos o bien referencias a objetos inmutables. Si su valor fuera una referencia a un objeto mutable dicho objeto referenciado puede ser modificado mientras que la referencia no lo que lleva a resultados desastrosos.
Por ejemplo, un array de longitud cero es siempre mutable por lo que siempre es erróneo declararlo como public static final. Si una clase tuviera un campo así, los clientes de esta clase podrían modificar el contenido del array lo que suele ser fuente de agujeros de seguridad:
//Riesgo de seguridad potencial !!
public static final Tipo[] VALORES = { … }; El array anterior debiera ser reemplazado por un array privado y una lista pública (inmutable):
private static final Tipo[] VALORES_PRIVADOS = { … };
public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES)); Otra alternativa “segura en tiempo de compilación” pero con pérdida de rendimiento sería reemplazar el array público por un método público que devolviera una copia del array privado:
private static final Tipo[] VALORES_PRIVADOS = {…};
public static final Tipo[] values()
{
return (Tipo[]) PRIVATE_VALUES.clone();
} Resumiendo todo, debe reducirse siempre la accesibilidad tanto como sea posible. Despues de diseñar cuidadosamente una API pública mínima, debería repasarse cuales de las clases, interfaces o miembros forman parte de dicha API. Con excepción de los campos public static final, las clases públicas no deben tener campos públicos. Asegurar también que todos los objetos referenciados por campos public static final son inmutables.
Tags: Uncategorized
Solapar (override) el método equals es sencillo pero hay muchas maneras de equivocarse al hacerlo y las consecuencias pueden ser desastrosas.
La manera más fácil de evitar estos problemas es no solaparlo cuando sabemos que una instancia es igual solamente a ella misma. Esto pasa en los casos siguientes:
- Cada instancia de la clase es inherentemente única: Esto es cierto para clases que representan entidades activas en vez de valores, como por ejemplo, Thread. El método equals de Object tiene el comportamiento exactamente adecuado para estas clases.
- No es necesario tener una comprobación lógica de igualdad: Por ejemplo para java.util.Random no tendría sentido. Con la implementación de equals de Object es suficiente.
- Cuando una superclase ya ha solapado equals y el comportamiento heredado de la superclase ya es apropiado. Por ejemplo la implementación en Map que viene de AbstractMap.
- La clase es privada on package-privada y estamos seguros de que el método equals no se invocará nunca. Para este caso, y en prevención de invocaciones accidentales, una buena práctica es realizar la siguiente implementación:
public boolean equals(Object o)
{
throw new UnsupportedOperationException();
}
Por norma general solaparemos el método Object.equals cuando exista una noción de igualdad lógica distinta de la mera identidad entre objetos y una superclase no haya solapado ya el método equals con el comportamiento que se desea. Es el ejemplo con las clases Integer o Date: Cuando se comparan con equals no se quiere saber si los objetos comparados son la misma instancia si no si tienen el mismo valor.
Cuando solapamos el método equals debemos seguir las normas de la especifiación dada para java.lang.Object donde el método equals implementa una relación de equivalencia:
- Es reflexivo: Para cualquier valor x: x.equals(x) devuelve true. Es decir, un objeto debe ser igual a sí mismo. Aunque es difícil imaginar el no cumplir esta inconscientemente, si se violara y, por ejemplo, añadiéramos una instancia de la clase en cuestión a una colleción, el método contains() devolvería false para la instancia recién añadida como parámetro.
- Es simétrico: Para cualquier par de valores x, y: x.equals(y) devuelve true si y solo sí y.equals(x) devuelve true. A diferencia de la norma anterior no es tan difícil violar esta norma inconscientemente.
- Es transitivo: Para cualquier terna de valores x, y, z: Si x.equals(y) devuelve true y y.equals(z) devuelve true entonces x.equals(z) devuelve true.
- Es consistente: Para cualquier par de valores x, y: Múltiples invocaciones de x.equals(y) deben devolver el mismo valor si los objetos referenciados por x e y no han sido modificados.
- Nulidad: Para cualquier valor no nulo x: x.equals(null) debe devolver false. La prueba directa para implementar nuestro método equals sería:
public boolean equals(Object o)
{
if(o==null)
return false;
...
}
Aunque este test no es necesario porque normalmente debemos comprobar que el argumento de equals se adapta al tipo adecuado y esto ya incluye el control de nulidad:
public boolean equals(Object o)
{
if(o instanceof MiTipo)
return false;
...
}
Estas directrices no hay que violarlas porque forman parte de la especificación y permiten implementar solapamientos del método equals que permitan luego, por ejemplo, que la semántica de métodos como contains no se vean afectadas.
Los puntos básicos que debemos tener en cuenta a la hora de implementar el método equals son:
- Usar el operador == para comprobar si el objeto argumento es una referencia al objeto. Si lo es devolver true.
- Usar el operador instanceof para comprobar que el argumento es del tipo adecuado. Si no lo es, devolver false.
- Hacer cast del argumento al tipo adecuado. Este cast viene precedido de la comprobación anterior con el operador instanceof por lo que siempre se realizará con éxito.
- Para cada argumento que tenga significado en la lógica de comparación, comprobar si el campo del objeto argumento concuerda. Para tipos básicos utilizaremos ==, para objetos el método equals.
- Cuando hayamos implementado el método equals preguntarse si simétrico, transitivo y consistente (ya que el resto de propiedades, tal como se ha dicho, es bastante difícil no cumplirlas).
Y finalmente:
Solapa el método hashCode() siempre que solapes equals().
No hay que basar la igualdad en recursos no fiables, por ejemplo, que requieran acceso a redes.
No sustituir Object por otro tipo en la declaración de equals ya que esto no solapa (override) el método Object.equals si no que lo sobrecarga (overload).
Tags: Uncategorized
En vez de utilizar la forma habitual de instaciar objetos a través de constructores podemos hacer que la clase en cuestión proporcione un método público static factory que simplemente devuelve una instacia de la clase. También se pueden seguir utilizando constructores en esta clase al mismo tiempo.
//uso de un constructor
MyObject myobj = new MyObject()
//uso de un método static factory
MyObject myobj = MyObject.getInstance(); Una ventaja de usar métodos static factory es que a diferencia de los constructores tienen nombre por lo que pueden describir de forma más específica la acción a realizar y por tanto hacer el código más legible sobre todo en el caso de que existen varios constructores con mismo número de parámetros en los que solo varía el tipo y/o orden de los mismos.
Una segunda ventaja es que, a diferencia de los constructores, los métodos static factory no requieren que se cree un nuevo objeto cada vez que se invocan: Un método static factory puede devover repetidamente el mismo objeto en distintas invocaciones lo que ayuda a controlar cuantas instancias existen al mismo tiempo y garantizar que una clase es un singleton. Además, para una clase inmutable permite asegurar que no existen dos instancias iguales de la misma y por tanto a.equals(b) si y solo sí a==b por lo que podemos usar el operador “==” en vez del método “equals()” obteniendo así una sustancial mejora en el rendimiento.
Una tercera ventaja de los métodos static factory sobre los constructores es que pueden devolver un objeto de cualquier subtipo. Si aprovechamos esta ventaja para la construcción de una API podemos devolver objetos sin hacer sus clases públicas.
La principal desventaja de los métodos static factory es que sobre las clases sin constructores públicos o protegidos no pueden derivarse subclases.
Otra desventaja es que leyendo el código o en la documentación (javadoc) no se distinguen en nada del resto de los métodos estáticos (como sí pasa con los constructores). Esta desventaja puede ser reducida mediante el uso de convenciones de código y normalmente hay dos nombres muy comunes para métodos static factory:
- valueOf — para devolver una instancia con “el mismo valor” que sus parámetros (conversión de tipos).
- getInstance — para devolver una instancia descrita por sus parámetros.
Vistas las ventajas y desventajas de este tipo de métodos solo queda decir que cuando, en un caso concreto, no estemos seguros de cual de las dos técnicas utilizar probablemente sea mejor opción utilizar constructores públicos simplemente porque es lo más normal.
Tags: Uncategorized