Los patrones de diseño (Design Patterns) son modelos de trabajo enfocados a dividir un problema en partes de manera de abordar cada una de ellas por separado para simplificar su resolución.
Los patrones de diseño son aplicables a cualquier lenguaje de programación orientado a objetos (no sólo a Java).
Historia
Desde principios de los ‘80s cuando Smalltalk era referente de la programación orientada a objetos y C++ estaba todavía en pañales, se empezaron a buscar modelos como el popular MVC (Model View Controller) encaminados a la división de un problema en partes para poder analizar cada una por separado.
Los patrones de diseño empezaron a tomar auge a partir de las descripciones de distintos autores a principios de ‘90s. Esto culmina en el año 1995 con la publicación de “Design Patterns — Elements of Reusable Software” de Gamma, Helm, Johnson y Vlissides; que puede considerarse como el más importante libro sobre patrones de diseño hasta el momento.
Un patrón de diseño es un conjunto de reglas que describen cómo afrontar tareas y solucionar problemas que surgen durante el desarrollo de software.
Se consideran tres conjuntos de patrones según su objetivo:
• Patrones de creación: Crean objetos de manera de no tener que que instanciarlos directamente, proporcionando mayor flexibilidad para decidir que objetos usar.
• Patrones escructurales: Permiten crear grupos de objetos para realizar tareas complejas.
• Patrones de comportamiento: Permite definir la comunicación entre los objetos de nuestro sistema y el flujo de la información entre los mismos.
Patrones de Creación
Se encargan de crear instancias de objetos. Los patrones de creación más conocidos son:
-
Factory
-
Abstract Factory
-
Builder
-
Prototype
-
Singleton
Factory
El objetivo de este patrón de diseño es devolver una instancia de múltiples tipos de objetos, generalmente heredan de una misma clase padre y se diferencian entre ellos por su comportamiento.
El objeto Factory es el encargado de decidir, según los parámetros que le pasemos, el tipo de objeto que devolverá.
Abstract Factory
Este patrón añade un nivel más de complejidad. Anteriormente la clase Factory devolvía objetos de diferentes tipos, en este caso, devolverá diferentes clases Factory según el parámetro enviado.
Por ejemplo: En un sistema podemos tener una clase Abstract Factory que devuelva diferentes objetos Look&Feel específicos para una plataforma ( Windows, Linux, Mac, … ). A su vez, estos objetos pueden ser clases Factory que devuelven los diferentes componentes correspondientes a cada una de esas plataformas.
Singleton
Es uno de los más utilizados y comúnmente encontrado en bibliografía sobre Java. Un Singleton es una clase de la que tan sólo puede haber una única instancia. Ejemplos típicos de esto son spools de impresión, servidores de bases de datos, etc..
Estas son las formas más comunes de encarar estos problemas:
• Crear una variable estática dentro de la clase que indique si una instancia ha sido o no creada. Esta solución tiene el problema de como avisar desde el constructor de que no se ha podido crear una nueva instancia.
• Crear una clase final: El objetivo de esto es crear una clase final que tan sólo tenga métodos estáticos. De este modo la clase no se podrá extender. Un ejemplo de esto es la clase java.lang.Math, que agrupa métodos matemáticos de utilidad de manera que sólo haya una única forma de acceder a los mismos.
• Crear el Singleton con un método estático: Esta aproximación lo que hace es hacer privado el constructor de la clase de manera que la única forma de conseguir una instancia de la misma sea con un método estático.
Patrones Estructurales
Los patrones estructurales describen cómo formar estructuras complejas a partir de elementos más simples. Existen dos tipos: de clase y de objeto.
Los patrones de clase muestran cómo la herencia puede ser utilizada para proporcionar mayor funcionalidad mientras que los patrones de objeto utilizan composición de objetos o inclusión de objetos dentro de otros para proporcionar también una mayor funcionalidad.
Los patrones más conocidos son:
-
Adapter
-
Bridge
-
Composite
-
Decorator
-
FaÇade
-
Flyweight
-
Proxy
Adapter
Este patrón es de los más conocidos y utilizados dentro y fuera de Java. Su finalidad es transformar la interfaz de programación de una clase en otra. Se utilizan adaptadores cuando se desea que clases que no tienen nada que ver funcionen de la misma manera para un programa determinado.
El concepto es escribir una nueva clase con la interfaz de programación deseada y hacer que se comunique con la clase cuya interfaz de programación era diferente.
Existen dos formas de realizar esto, con herencia o con composición de objetos. En el primer caso vamos a crear una nueva clase que heredará de la que se desea adaptar y a esta nueva clase se le agregarán los métodos necesarios para que su interfaz de programación se corresponda con la que se desea utilizar.
En la segunda aproximación se incluye la clase original dentro de la nueva y se crean los métodos de manera que accedan a la clase que se agregaron como atributo. Estas dos formas se corresponden con los términos de adaptadores de objeto y adaptadores de clase.
Bridge
Un Bridge se utiliza para separar la interfaz de una clase de su implementación de forma que ambas puedan ser modificadas de manera separada, el objetivo es poder modificar la implementación de la clase sin tener que modificar el código del cliente de la misma.
El funcionamiento del Bridge es simple, supongamos datos de clientes de nuestra empresa que se desean mostrar en pantalla. En una opción de nuestro programa queremos mostrar esos datos en una lista con sus nombres, mientras que en otra queremos mostrar los datos en una tabla con nombre y apellidos.
Lo importante de este patrón es que si ahora quisiésemos mostrar información de nuestros clientes en un árbol, no tendríamos que modificar el cliente para nada sino que en nuestro Bridge añadiríamos un parámetro que crearía el árbol.
Proxy
Un patrón Proxy cambia un objeto complejo por otro más simple. Proxy permite posponer la creación de un objeto hasta que sea realmente necesitado. Un Proxy tiene, generalmente, los mismos métodos que el objeto al que representa pero estos métodos solamente son llamados cuando el objeto ha sido cargado por completo.
Aplicación:
• Un objeto, como una imagen grande, puede tardar mucho en cargarse.
• Un objeto se encuentra en una máquina remota solamente accesible por red.
• El objeto tiene el acceso restringido el Proxy puede encargarse de validar los permisos.
Supongamos una interfaz como el de cualquier navegador de internet y que tenemos un panel en el cual queremos mostrar una imagen que es muy grande. Como sabemos que va a tardar bastante en cargarse utilizaremos un Proxy.
En este caso se abre un nuevo hilo en el que utilizando el MediaTracker intenta cargar la imagen. Mientras en el método paint() comprueba si se ha cargado la imagen, en caso afirmativo la muestra y si todavía no hemos podido cargarla muestra un rectángulo vacío.
Patrones de Comportamiento
Los patrones de comportamiento fundamentalmente especifican el comportamiento entre los objetos del sistema. Los patrones más conocidos son:
-
Chain
-
Observer
-
Mediator
-
Template
-
Interpreter
-
Strategy
-
Visitor
-
State
-
Command
-
Iterator
Command
El patrón Command especifica una forma simple de separar la ejecución de un comando del entorno que generó dicho comando. La razón de utilizar este patrón es que muchas veces en una interfaz podemos encontrar que el número de eventos que pueden producirse es considerablemente grande y originaría un código muy confuso y extenso.
Además no parece demasiado orientado a objetos él que las acciones sean elegidas y controladas por el interfaz de usuario sino que deberían ser independientes.
Una forma para corregir esto es el patrón Command que consta de un método Execute() que se llama siempre que se produzca una acción sobre dicho objeto. La interfaz de este patrón no indica qué acción estamos realizando, es decir, hemos conseguido separar nuestras acciones del interfaz gráfico (por ejemplo).
Ahora ya podemos crear un método execute() para cada uno de los objetos que quiera realizar una acción de modo que sea dicho objeto el que controle su funcionamiento y no otra parte cualquiera del programa.
Iterator
Este patrón es uno de los más simples y más frecuentes. Su función es permitir recorrer un conjunto de datos mediante una interfaz estandar sin tener que conocer los detalles de la implementación de los datos.
Además siempre podremos definir iteradores especiales que realicen algún tipo de acción con los datos o que sólo devuelvan elementos determinados. En java este patrón se encuentra implementado en las interfaces Enumeration e Iterator.
Una desventaja de esta interfaz respecto a la implementación de Iterator en otros lenguajes es que no posee métodos para ir al principio y al final de la enumeración de modo que si se quiere volver a empezar habrá que adquirir una nueva instancia de la clase.
En Java existe el patrón Enumeration presente en clases como Vector o Hashtable. Para obtener una enumeración de todos los elementos de un vector se debe llamar al método elements() que devuelve un tipo Enumeration. Este método elements() es un método que ejerce como Factory devolviendo distintas instancias de tipo Enumeration.
Una opción muy interesante es añadir filtros de iteración de modo que antes de devolver una enumeración con los elementos realicemos cualquier otro tipo de acción con ellos como por ejemplo una ordenación por algún criterio.
Observer
En la actualidad es común encontrar aplicaciones que muestran simultáneamente un conjunto de datos de múltiples formas diferentes en el mismo interfaz (por ejemplo en forma de árbol, tabla, lista, … ).
El patrón Observer asume que el objeto que contiene los datos es independiente de los objetos que muestran los datos de manera que son estos objetos los que “observan” los cambios en dichos datos:
Al implementar el patrón Observer el objeto que posee los datos se conoce como sujeto mientras que cada una de las vistas se conocen como observadores. Cada uno de estos observadores, registra su interés en los datos llamando a un método público del sujeto.
Entonces, cuando estos datos cambian, el sujeto llama a un método conocido de la interfaz de los observadores de modo que estos reciben el mensaje de que los datos han cambiado y actualizan la vista.
En Swing los objetos JList, JTable y JTree actúan como observadores de un modelo de datos. De hecho todos los objetos que deriven de JComponent pueden realizar esta separación entre vista y datos, esto se conoce como arquitectura MVC (Modelo-Vista-Controlador), donde los datos son representados por el modelo y la vista por el componente visual.
En este caso el controlador es la comunicación entre el modelo y la vista.
Strategy
El patrón Strategy consiste en un conjunto de algoritmos encapsulados en un contexto determinado Context. El cliente puede elegir el algoritmo que prefiera de entre los disponibles o puede ser el mismo objeto Context el que elija el más apropiado para cada situación. Cualquier programa que ofrezca un servicio o función determinada, la cual puede ser realizada de varias maneras, es candidato a utilizar el patrón Strategy.
Puede haber cualquier número de estrategias y cualquiera de ellas podrá ser intercambiada por otra en cualquier momento. Hay muchos casos en los que este patrón puede ser útil:
• Grabar ficheros en diferentes formatos
• Utilizar diferentes algoritmos de compresión
• Capturar video utilizando esquemas de captura diferentes
• Mostrar datos utilizando formatos diversos
Visitor
Este patrón aparenta romper con la programación orientada a objetos en el sentido de que crea una clase externa que va a actuar sobre los datos de otras clases.
Imaginemos que tenemos varios objetos gráficos con métodos de dibujo muy similares. Los métodos de dibujo pueden ser diferentes pero seguramente haya una serie de funciones comunes en todos ellos, funciones que probablemente tendremos que repetir una y otra vez en su código, probablemente tendríamos un esquema así:
Sin embargo si escribimos una clase Visitor será ella la que contenga los métodos de dibujo y tendrá que ir “visitando” los objetos en orden y dibujándolos:
Antonio VB