Patrones de Diseño de Software: Tipos, Ejemplos y Aplicaciones
Enviado por Programa Chuletas y clasificado en Informática y Telecomunicaciones
Escrito el en español con un tamaño de 310,88 KB
Patrones de Diseño de Software
Fundamentales
Interfaz
Descripción
Se utiliza cuando deseamos que una clase que hace uso de los servicios proporcionados por otras clases, permanezca independiente de estas.
Ventajas:
- Reduce el acoplamiento entre clases.
- Evita la propagación de cambios.
Implementación:
A través de la utilización de interfaces. Una interfaz define un contrato que deben cumplir una serie de clases, independientemente de los detalles internos de implementación de cada una de éstas. Se trata de un tipo especial de herencia denominado "de interfaz". Mediante la aplicación de este patrón de diseño, podemos independizar una clase de otra que le presta servicios, haciendo que no tenga una referencia a la clase que ofrece el servicio, sino a la interfaz que define el contrato del servicio a prestar.
Ejemplo
¿Cómo podemos implementar este patrón de diseño con Visual Basic .NET? En VB.NET podemos definir una interfaz de la siguiente manera:
Esta interfaz define un comportamiento, que podrán heredar otras clases a través de la herencia de interfaz.
En el Listado 1 tenemos dos implementaciones de esta interfaz con VB.NET. En dicho ejemplo tenemos una interfaz "IEducado", que obliga a que todas las clases que la implementen tengan métodos de saludo y despedida, impresos por consola.
En el Listado 2 vemos como la clase EducadoIngles implementa estos métodos en inglés, mientras que la clase EducadoEspañol los implementa en español.
Delegación
Descripción
La delegación consiste en un medio de extender y reutilizar la funcionalidad de una clase mediante la creación de otra clase que se la proporcione. Todos los lenguajes orientados a objetos proporcionan mecanismos para implementar este patrón, pues basta con añadir una nueva referencia a la clase que consume el servicio.
La importancia del patrón Delegación radica en que nos proporciona un mecanismo para decidir cuándo debemos hacer que una clase herede de otra, o cuando añadirle una referencia para utilizar sus servicios. En nuestro ejemplo de la clase educada, parece claro que hay que utilizar delegación. Muchas veces la decisión nos la da el sentido común (la regla de "es un"), mientras que otras veces habrá que hacer un análisis del número de herencias posibles. En nuestro ejemplo, añadiendo una referencia a la interfaz "IEducado" sólo crearemos una clase que consuma sus servicios, mientras que si optamos por el mecanismo de la herencia, tendríamos que crear dos subclases: una de "EducadoIngles" y otra de "EducadoEspañol". Además, conforme fuese creciendo el número de implementaciones de "IEducado", habría que ir aumentando el número de herencias.
El listado siguiente muestra la codificación en VB.NET de este patrón.
De Creación
Fábrica Abstracta
Descripción
Es un modelo que utiliza abstracción de clases para crear y relacionar objetos sin conocer de qué clase son. Proporciona una interfaz para crear familias de objetos relacionados o dependientes sin especificar su clase concreta.
Ejemplo
El siguiente ejemplo muestra un AbstractFactory que crea elementos de interfaz gráfica para distintos sistemas operativos.
Builder
Descripción
Este patrón se llama Builder porque está relacionado con la construcción de objetos complejos, posiblemente de diferentes fuentes. Conforme aumenta la complejidad de la creación de objetos, gestionarla desde el método constructor puede llegar a ser difícil. Esto es especialmente cierto si el objeto no depende exclusivamente de los recursos que están bajo su control.
Ejemplo
El siguiente ejemplo va ensamblando los componentes de un computador según el uso que le queramos dar.
De Partición
Composite
Descripción
El patrón Composite sirve para construir objetos complejos a partir de otros más simples y similares entre sí, gracias a la composición recursiva y a una estructura en forma de árbol.
Esto simplifica el tratamiento de los objetos creados, ya que al poseer todos ellos una interfaz común, se tratan todos de la misma manera.
Ejemplo
Un objeto cuya clase es GrupoDeImágenes podría contener un Cuadrado, un Triángulo y otro GrupoDeImágenes, este grupo de imágenes podría contener un Círculo y un Cuadrado. Posteriormente, a este último grupo se le podría añadir otro GrupoDeImágenes, generando una estructura de composición recursiva en árbol, por medio de muy poca codificación y un diagrama sencillo y claro.
Filter
Descripción
- Evitar recepción de mensajes no deseados.
- Crear un conjunto de criterios (filtros) de manera que si el mensaje contiene propiedades que hacen matching con los filtros especificados, este será enviado por un canal de salida; en caso contrario será descartado.
Ejemplo
Podría haber un filtro de depuración que envuelva un filtro de autenticación. Los siguientes fragmentos de código muestran como se podrían crear estos mecanismos de forma programática:
DebuggingFilter:
public class DebuggingFilter implements Processor { private Processor target; public DebuggingFilter(Processor myTarget) { target = myTarget; } public void execute(ServletRequest req, ServletResponse res) throws IOException, ServletException { //Do some filter processing here, such as displaying request parameters target.execute(req, res); } }
CoreProcessor:
public class CoreProcessor implements Processor { private Processor target; public CoreProcessor() { this(null); } public CoreProcessor(Processor myTarget) { target = myTarget; } public void execute(ServletRequest req, ServletResponse res) throws IOException, ServletException { //Do core processing here } }
En el controlador servlet, hemos delegado en un método llamado processRequest para manejar las peticiones entrantes:
public void processRequest(ServletRequest req, ServletResponse res) throws IOException, ServletException { Processor processors = new DebuggingFilter(new AuthenticationFilter(new CoreProcessor())); processors.execute(req, res); //Then dispatch to next resource, which is probably the View to display dispatcher.dispatch(req, res); }
Sólo para propósitos de ejemplo, imagina que cada componente de procesamiento escribe en la salida estándar cuando se ejecuta. El siguiente ejemplo muestra la posible salida de la ejecución:
Debugging filter preprocessing completed... Authentication filter processing completed... Core processing completed... Debugging filter post-processing completed...
Se ejecuta una cadena de procesadores en orden. Cada procesador, excepto el último de la cadena, se considera un filtro. En el componente procesador final es donde se encapsula el procesamiento principal que queremos completar para cada petición. Con este diseño, necesitaremos cambiar el código de la clase CoreProcessor, así como cualquier clase de filtro, cuando queramos modificar la forma de manejar las peticiones.
Estructurales
Adaptador (Adapter)
Descripción
Convierte la interfaz de una clase en otra distinta que es la que esperan los clientes. Permiten que cooperen clases que de otra manera no podrían por tener interfaces incompatibles.
Ejemplo
Envoltorio (Decorator)
Descripción
Añade dinámicamente nuevas responsabilidades a un objeto, proporcionando una alternativa flexible a la herencia para extender la funcionalidad. Esto nos permite no tener que crear sucesivas clases que hereden de la primera incorporando la nueva funcionalidad, sino otras que la implementan y se asocian a la primera.
Ejemplo en JAVA
abstract class Componente { abstract public void operacion(); } class ComponenteConcreto extends Componente { public void operacion() { System.out.println("ConcreteComponent.Operation()"); } } abstract class Decorador extends Componente { protected Componente componente; public void setComponente(Componente component) { this.componente = component; } public void operacion() { if (componente != null) componente.operacion(); } } class DecoradorConcretoA extends Decorador { private String estadoAgregado; public void operacion() { super.operacion(); estadoAgregado = "Nuevo estado"; System.out.println("DecoradorConcretoA.Operacion()"); } } class DecoradorConcretoB extends Decorador { public void operacion() { super.operacion(); AgregarComportamiento(); System.out.println("DecoradorConcretoB.Operacion()"); } void AgregarComportamiento() { System.out.println("comportamiento añadido B"); } } public class Cliente { public static void main(String[] args) { ComponenteConcreto c = new ComponenteConcreto(); DecoradorConcretoA d1 = new DecoradorConcretoA(); DecoradorConcretoB d2 = new DecoradorConcretoB(); d1.setComponente(c); d2.setComponente(d1); d2.operacion(); } }
De Comportamiento
Cadena de Responsabilidad
Descripción
Es un patrón de diseño consistente en objetos de comando y varios objetos de procesamiento. Cada objeto de comando, tiene la lógica de lo que puede hacer y de lo que no, y como pasarle la responsabilidad al objeto siguiente en la cadena.
Este patrón divide las tareas en objetos cuidando que cada uno de ellos haga solo lo que es su tarea. Es decir, no extralimitarse en sus funciones.
Ejemplo
Se habla de un sistema de monitoreo, en donde la responsabilidad del servidor central es guardar todos los eventos que suceden en varios sensores que están conectados a él.
Para esto, los sensores le informan sobre cambios que ocurran. El servidor no es quien está al tanto de los cambios; solo los registra.
Observer
Descripción
En un toolkit de GUI, separar los objetos de presentación (vistas) de los objetos de datos, de forma que se puedan tener varias vistas sincronizadas de los mismos datos (editor-subscriptor). Su objetivo es definir una dependencia 1:n de forma que cuando el objeto 1 cambie su estado, los objetos sean notificados y se actualicen automáticamente.
Ejemplo (Hoja de cálculo)
- Separar los datos de la presentación.
- La hoja de cálculo y los gráficos muestran los mismos datos con diferentes presentaciones.
- La hoja de cálculo y los gráficos no se conocen entre sí.
- Reusar separadamente las clases de los datos de las clases de presentación.
- No se conocen entre sí, pero se comportan como si se conociesen: si el usuario cambia datos en la hoja entonces se actualizan los gráficos, y viceversa.
- Los gráficos y la hoja dependen de los datos.
- Deben ser notificados.
De Concurrencia
Balking
Descripción
La solución que plantea este patrón de diseño, es simplemente ignorar la llamada y es por eso que sólo se puede aplicar en entornos donde la ejecución no sea crítica dentro del sistema. No podría aplicarse en una situación tan crítica como es, por ejemplo, el latido de un marcapasos.
Ejemplo
El ejemplo que se eligió para analizar este patrón, es la interacción entre un retrete y las distintas formas que pueden existir de activar su vaciado. Ejemplos de formas de activación son un botón o un sensor de proximidad.
Evidentemente, no tiene sentido iniciar un vaciado una vez que otro vaciado se encuentra en curso. Una solución posible es verificar el estado del retrete antes de realizar el pedido de vaciado. El problema con esta solución es que se necesita introducir lógica que no pertenece al interés principal del cliente, que es simplemente realizar el vaciado del retrete. Es por eso que se propone encapsular en un aspecto dicho mecanismo para poder aplicarlo de una forma transparente al cliente.
Optimistic Locking
Descripción
El acceso y modificación concurrente del contenido de un recurso compartido (por ejemplo una tabla de una base de datos), puede generar inconsistencia en el contenido de dicho recurso, si no se toman las precauciones pertinentes. El bloqueo optimista es una solución intermedia que supone el caso más optimista (nadie modificó el elemento desde la última vez que se accedió) y se toman acciones en caso de que dicho supuesto sea incorrecto.
Ejemplo
El caso de estudio que se ha elegido para este patrón es el acceso concurrente a un registro genérico en una tabla de una base de datos. Dicho registro solo contiene un atributo a modo de ejemplo y una clave primaria.