Con la colaboración de JavaHispano
Puedes encontrar la versión original en esta dirección
Como explicamos en capÃtulos anteriores, hay tres tipos fundamentales de patrones:
- patrones de creación.
- patrones estructurales.
- patrones de comportamiento.
En los artÃculos anteriores explicamos, a parte de algunos conceptos básicos e historia, algunos de los patrones de creación. En esta cuarta entrega sobre el diseño de software con patrones vamos a empezar con el siguiente tipo, los patrones estructurales, que se encargan de modelar las relaciones entre objetos para formar estructuras más o menos complejas.
Patrones estructurales
Como he dicho un par de lÃneas más arriba, los patrones estructurales se ocupan de la combinación de objetos para crear estructuras complejas. Esto no es del todo exacto, ya que hay patrones estructurales que se encargan de las relaciones entre clases (mayoritariamente el uso de la herencia), mientras que otros se encargan de los objetos, las instancias de las clases (normalmente composición).
Entre los patrones que tengo intención de mencionar, sin que sea una lista ya definida, pueden destacar el patrón adaptador (adapter pattern, GoF), el patrón fachada (facade pattern, GoF), el patrón proxy (proxy pattern, GoF) y el patrón puente (bridge pattern, GoF).
El patrón adaptador (Adapter pattern, GoF)
El patrón adaptador, como su propio nombre nos puede hacer ver, se usa para convertir el interface de una clase a la de otra, entendiendo en este caso interface como el conjunto de métodos que ofrece una clase para su manipulación por código, no la herramienta que usa el usuario final de nuestra aplicación. Lo que haremos será escribir métodos con nuestra interface deseada que deleguen el trabajo en los de la interface ya existente.
Las razones para querer hacer algo asà pueden ser de muy distinta naturaleza, y pueden ir desde querer integrar una clase ya existente pero que no podemos modificar en una nueva jerarquÃa de clases, que por ejemplo implemente una nueva interfaz, podemos querer limitar el interface de una clase, hasta poder querer hacerlo simplemente porque la convención de nombres usada en la clase original, que tampoco podemos o queremos cambiar, no se ajusta a la nuestra. Esta última razón os puede parece un poco tonta, pero os aseguro que puede ser bastante frustrante recibir miles de errores de compilación por trabajar con métodos del estilo GetName en lugar de getName.
Esta adaptación la podemos realizar de dos formas:
- Por herencia.
- Por composición.
Adaptación por herencia
Si optamos por usar la herencia para adaptar una clase, haremos que la clase que implementa la nueva interface extienda de la original y añadiremos a esta los métodos que consideremos oportunos.
Supongamos que alguien un poco cazurro ha escrito la siguiente clase:
public class Usuario{ protected String nombre; protected String password; . . . public Usuario (String nombre, String password){ . . . } public String GetNombre (){ return nombre; } public String GetPassword (){ return password; } . . . }
Como nos hemos leido las convenciones de código de Java de nuestra empresa y estamos de acuerdo en que es bueno seguirlas, nos damos cuenta de que esas dos G de Getxxx nos dan muchos quebraderos de cabeza. El problema esta en que esa clase ya esta en sistemas productivos y por tanto no podemos hacer algo tan simple como cambiar esa letra. Es por eso que optamos por extender esa clase, de forma que el sistema siga trabajando correctamente con las clases antiguas, pero nosotros podamos evitar acordarnos de la santa madre de alguno cada vez que nos aparezcan veinte errores de compilación por algo tan tonto.
public class BuenUsuario extends Usuario{ public BuenUsuario (String nombre, String password){ super (nombre, password); } public String geNombre (){ return GetNombre(); } public String getPassword (){ return GetPassword(); } }
Además, el haberlo hecho asÃ, aprovechándonos de las ventajas de la herencia, hace que los métodos de otras clases que usen la clase Usuario puedan seguir funcionando sin retocarlos.
Adaptación por composición
Pero ocurre que en algunos casos no podemos usar la herencia, ya sea porque queremos integrar esa clase en una jerarquÃa nueva y ya estemos heredando de otra clase, o ya sea por causa de modificadores de acceso como private o final que impidan que se hereden algunas caracterÃsticas de la clase, o simplemente que no queramos que la nueva clase tenga el interface completo de la original. En ese caso tendremos que usar la composición, es decir, incluir la clase a adaptar como un atributo de la adaptadora.
Supongamos que no existe la clase java.util.Stack y que queremos crear una clase con estructura de pila para nuestros programas. Podemos, por qué no, implementar una lista enlazada nosotros mismos y crear nuestra propia pila, pero entonces deberiamos tener cuidado de muchas cosas, como por ejemplo de los accesos concurrentes. Es por eso que viendo que la clase java.util.Vector ya tiene implementado todo eso, y de forma bastante correcta, se nos ocurre que un vector serÃa una buena pila si no tuviera tantos métodos que podriamos usar para saltarnos el concepto de pila. Lo siguiente que se nos debe ocurrir es crear una clase adaptadora que forme una pila a partir de un vector:
public class Pila{ private Vector valores; public Pila(){ valores = new Vector(); } public void push (Object o){ valores.add (o); } public Object pop (){ Object o = valores.lastElement(); valores.remove (o); return o; } }
Por supuesto habrÃa que tener más cuidado al sacar elementos (método pop), pero en fin, el objetivo de este código no es crear una clase de utilidad real.
El patrón fachada (Facade pattern, GoF)
El patrón fachada trata de simplificar la interface entre dos sistemas o componentes de software ocultando un sistema complejo detrás de una clase que hace las veces de pantalla o fachada.
La idea principal es la de ocultar todo lo posible la complejidad de un sistema, el conjunto de clases o componentes que lo forman, de forma que solo se ofrezca un (o unos pocos) punto de entrada al sistema tapado por la fachada.
Una ventaja más de usar una clase fachada para comunicar las dos partes o componentes, es la de aislar los posibles cambios que se puedan producir en alguna de las partes. Si cambias, por porner un ejemplo, el medio de comunicación o de almacenamiento de una de las partes, la otra, que por ejemplo hace la presentación, no tiene porque enterarse, y viceversa.
Este patrón es bastante fácil de explicar en palabras (aunque no estoy seguro de haberlo conseguido), pero no tanto de poner un ejemplo, al menos no uno completo. Intentaré hacerlo basándome en un caso personal.
En el trabajo tenÃamos un sistema de varias capas, un servidor DMS (Document Managment System), un proxy para distintos DMS escrito en C/C++ y una aplicación web desarrollada con servlets (y motores de plantillas) como interface al usuario. El aspecto más peliagudo era la comunicación entre el proxy para DMS y nuestra aplicación Java, que se producÃa por medio de sockets TCP/IP y mensajes XML. Podeis imaginaros que entre las clases propias de la comunicación (sockets, streams, handlers de la comunicación, etc), las clases de proceso del XML (parsers, codificadores, conversores xml a PDF, etc) y las clases de nuestro sistema (documentos, listas de resultados, etc) resultaba un sistema bastante complejo, complejidad que no era necesario que la conociera la aplicación web.
En ese escenario creamos una clase fachada que ocultaba la complejidad de la comunicación de la siguiente forma:
public class XArCFacade{ . . . public Document getDocument (String id){ . . . } . . . public HitList search (String request){ . . . } . . . }
Obviamente esta clase esta reducida en cuanto al número de métodos respecto a la original, pero sigue la misma idea. Como veis, nuestra aplicación no tiene porque saber ni como ni de donde se consigue la información, nuestra aplicación web solo tenÃa que preocuparse de buscar la mejor manera posible de presentar dicha información, que tampoco fue fácil.
Como hemos dicho antes, esta arquitectura aisla los posibles cambios en una de la partes. En nuestro caso, se cambió la presenteción de JSP a Servlets con plantillas, y la parte de comunicación se cambió de usar una libreria escrita en C/C++ por medio de JNI a usar una pure java, de forma independiente, sin que la otra se viera afectada.