Federico Alemany
Patrones de Diseño (GRASP):
Escrito por Federico Alemany - 10 de diciembre de 2014
En este articulo voy a tratar de resumir (muy brevemente) los patrones de diseño conocidos como Patrones GRASP. El objetivo es lograr explicar de manera muy básica y rápida cuales son los principios de estos patrones, y porque es tan recomendable tenerlos en cuenta a la hora de diseñar software.
Que es un patrón de diseño: Empecemos por entender que es un patrón. Un patrón es un conjunto de "elementos" que se repiten de manera predecible. Alguna vez, seguramente escuchamos hablar de "Patrones de Comportamiento", haciendo referencia a un determinado comportamiento adoptado por las personas o sociedades ante algún fenómeno específico (por ejemplo, en economía: cuando hay crisis, la gente gasta menos). Es decir, un patrón es un par causa/consecuencia, fenómeno/efecto o problema/solución.
Ahora bien, los patrones de diseño son situaciones o problemas observados de manera recurrente, y asociados a ellas, una solución optima (o al menos sumamente efectiva).
Por ejemplo, todas las aplicaciones informáticas (o la inmensa mayoría) necesita de una conexión a una base de datos... eso es un fenómeno común a todas las aplicaciones, un PATRON.
Cuando existen situaciones o problemas comunes, y ampliamente observados, surgen soluciones que pueden aplicarse, basándose en una resolución anterior.
En lenguaje coloquial, esto sería algo así como: "Este problema tiene la misma forma que uno que resolví exitosamente ayer, entonces aplicaré la misma solución".
Esta forma de pensar, nos lleva a entender que los patrones son formas de proceder ante problemas o situaciones recurrentes y bien conocidas.
En este artículo voy a resumir brevemente, los patrones de diseño denominados "Patrones GRASP".
Se denominan "Patrones GRASP" por sus siglas en ingles: General Responsibility Assignment Software Patterns o Patrones Generales de Software para Asignación de Responsabilidades.
Como su nombre lo indica, estos patrones nos indican cual es la manera de asignar responsabilidades a objetos software.

Experto en información: el patrón "experto en información" nos sugiere que debemos asignar las responsabilidades a aquellos objetos (o clases de objetos) que disponen de la información para hacerlo. Pongamos un ejemplo (tomado del libro UML y Patrones): tenemos una venta (en un supermercado, por ejemplo), y esa venta está compuesta por un cliente, varios artículos, una fecha, y un monto total.
Ahora supongamos que tenemos que crear un método que nos devuelva el total de la venta: Quién es el responsable de realizar dicha tarea? La clase Venta conoce cada uno de los artículos que la integran, registrados en líneas de venta. Cada línea de venta, contiene un artículo, la cantidad, y su subtotal.



Bajo esta situación, podemos decir que el experto de información, para ofrecernos el total de la venta, es la misma venta, implementando el método obtener_total(). Pero observemos un detalle, la venta no conoce el subtotal de cada línea, por lo que podría existir un clase "LineaVenta" que implemente el método get_subtotal(). Al mismo tiempo, cada línea de venta no conoce el precio del artículo que lleva, así que debería enviar el mensaje get_precio() al objeto Articulo o Producto. Volviendo sobre nuestros pasos, el objeto "Articulo", retorna el precio propio, el objeto LineaVenta lo multiplica por la cantidad, y devuelve (al objeto venta) su subtotal. El objeto "Venta", suma todos los subtotales retornados por sus lineasVenta y devuelve el total.
Obsérvese que en este caso, el objeto venta, no realizó por si mismo todo el cálculo, sino que necesitó de dos objetos más: el objeto "LineaVenta" y el objeto "Articulo", ambos dos, "expertos en información" para las tareas que se les encomendó.
Un objeto puede ser "experto en información", pero necesitar para ello la colaboración de otros objetos.

:Creador:: el patrón "Creador" nos invita a discutir quien es el encargado (o quien debería tener la responsabilidad) de crear un determinado objeto.
Siguiendo el ejemplo anterior de la Venta, podríamos preguntarnos "Quién es el encargado de crear cada objeto "LineaVenta"? El patrón "Creador" nos sugiere que el encargado de esto debería ser aquel objeto (o aquella clase) que agregue o contenga instancias de la clase LineaVenta. En este caso, la venta es un "contenedor" de instancias LineaVenta, por lo que es candidata ideal (según el Creador) para tener un método crear_linea_venta(). El patrón creador sugiere encontrar clases de objetos que estén vinculadas (o que se conozcan) para hacerlas responsables de la creación de los objetos, manteniendo así un bajo acoplamiento (concepto que se trata a continuación).

:Bajo Acoplamiento:: cuando se habla de acoplamiento entre objetos, se hace referencia a la "fuerza" con la que ciertos objetos están relacionados, o dependen unos de otros. Mientras más dependencias tenga un objeto de otros para llevar a cabo sus tareas, más fuerte será el acoplamiento. Cuando una clase de objetos puede realizar sus tareas, sin depender de ninguna otra clase de objetos (o de un número muy reducido de ellas) se dice que hay bajo acoplamiento. El grado de acoplamiento es importante debido a que está relacionado íntimamente con la reutilización de clases de objetos. Una clase que depende de muchas otras es menos reutilizable (en el sentido de que, si se la quiere reutilizar, hay que reutilizar también todas las clases de las cuales depende). En el ejemplo de "Experto en Información", la clase venta está acoplada con la clase "LineaVenta", ya que depende de ella para poder resolver el método obtener_subtotal().
Este es un patrón (al igual que los demás) que no pueden ser considerados individualmente, sin considerar también otros patrones, y encontrando un equilibrio entre ellos. Tener un sistema con bajo acoplamiento reduce el impacto de los eventuales cambios. El bajo acoplamiento llevado al extremo no es deseable, ya que tendríamos un sistema de elementos individuales, y un sistema de objetos debe estar compuesto de objetos que se comunican y colaboran entre sí.

:Alta cohesión:: la cohesión se refiere al grado o la fuerza con que se relacionan algunos elementos. Un elemento con alta cohesión, realiza tareas relacionadas entre sí. Básicamente esto es "Los objetos deben realizar tareas coherentes y relacionadas entre sí". Un ejemplo sería: si tenemos un objeto "Venta", podríamos implementar métodos como "obtener_subtotal()" o "nueva_linea_venta()", es decir, métodos que tienen una relación fuerte con la venta. Por el contrario, un método "guardar_BD()" que se encarga de guardar la venta en una base de datos, generará una baja cohesión, ya que la venta debería conocer técnicas de almacenamiento del motor de base de datos que se esté utilizando. Este tipo de malas decisiones, ocasiona que objetos realicen muchas tareas poco comunes entre sí, y con el objeto. Lo ideal en este caso, para lograr alta cohesión, sería agrupar todas las funciones de acceso a la base de datos en una clase que se llame "ConexionBD" por ejemplo. En pocas palabras, para lograr alta cohesión debemos agrupar funciones similares en clases individuales... La clase Venta, realizando operaciones relacionadas a la venta, la clase "Pago" realizando operaciones relacionadas con pagos, la clase ConexionBD con lo propio, aunque en la práctica, la cohesión no puede considerarse individualmente sin considerar también el bajo acoplamiento y el patrón "experto en información".
"...Como regla empírica, una clase con alta cohesión tiene un número relativamente pequeño de métodos, con funcionalidad altamente relacionada, y no realiza mucho trabajo. Colabora con otros objetos para compartir el esfuerzo si la tarea es extensa..." (Craig Larman)

:Controlador:: El patrón "controlador" establece una clara separación entre la interfaz de usuario (una interfaz grafica, por ejemplo) y el corazón o núcleo de procesamiento de la aplicación, donde se halla la lógica de negocios.
La idea básica es crear una clase que implemente métodos dedicados a "escuchar" o "atender" los eventos del sistema. En un sistema basado en web, por ejemplo, la clase controlador se encuentra entre la interfaz grafica (en un cliente) y el código de la aplicación (en un servidor). El controlador se encarga de recibir peticiones de la interfaz grafica y delega el trabajo a alguna clase del dominio del problema. En entornos que no son basados en web, como en java por ejemplo, suele usarse un controlador cuando es necesario usar procedimientos remotos. El controlador se ubica entre el cliente remoto y el código de la aplicación java.
Volviendo al ejemplo de las ventas, supongamos que el usuario pulsa el botón "Finalizar Venta" (en la interfaz grafica del cliente web). Este evento es manejado por un "controlador" que delega dicha tarea a otras clases. En este ejemplo, puntualmente, dentro del código de la clase controlador puede haber un método llamado "finalizar_venta()", que delega su trabajo a los siguientes métodos: registrar_pago(): de la clase Pagos, modificar_stock(): de la clase Deposito, registrar_venta():de la clase Venta, etcétera.
La interfaz de usuario, no debería (según este patrón) direccionar directamente métodos de la lógica de negocios, logrando así un aumento de potencial para reutilizar y adaptar.
El controlador, teóricamente, no debería tampoco realizar muchas tareas; solo debe encargarse de recibir una solicitud o evento, y en consecuencia "llamar" al método encargado de dicha tarea.

:Polimorfismo:: el polimorfismo es la capacidad que tienen objetos de diferentes clases, a responder a un mismo mensaje. Esta capacidad requiere de un diseño en el cual no importe la clase de un objeto, sino el hecho de que sepa responder un determinado mensaje. El polimorfismo ayuda en gran medida a lograr un bajo acoplamiento. Continuando con el ejemplo anterior de la venta, supongamos que en la pantalla del operador aparece un detalle de cada producto que se está comprando (por ejemplo, precio unitario, descripción del producto, código ID, etc.). Imagine que existe un indicador llamado tamaño, pero existen productos líquidos, que deben mostrar por ejemplo "1 litro", mientras otros productos podrían mostrar "723 gramos" o "2 Kg", y en otras ocasiones, mostrar "15 unidades", y esto va a depender de la subclase a la que pertenezca cada artículo. Podríamos tener una clase padre llamada Artículo, con varias clases hijas, como ser Bebidas (litros), Limpieza (unidades), Carne (gramos o Kg), etc.
Esto nos lleva a pensar que podemos implementar un método llamado "get_tamaño()" en las cada una clases hijas.
Esto permite que cada objeto venta pueda enviar un mensaje get_tamaño() a cualquier ítem de su lista, con la seguridad de que (independientemente de la clase que sea) va a saber responder a dicho mensaje. Esto es el polimorfismo, métodos con implementaciones diferentes, respondiendo al mismo mensaje.

:Fabricación Pura:: Cuando modelamos situaciones de la vida real en objetos, existe un salto en la representación. Esto es, la diferencia entre las entidades del mundo real y los objetos software que creamos para resolver algún problema concreto. Por ejemplo, cuando hablamos de una venta, con artículos, clientes, pagos, etc. nos referimos a entidades del mundo real, por lo que, seguramente, cuando pensemos en objetos software pensaremos en un objeto "Venta", un objeto "Cliente", un objeto "Articulo", etc. Si logramos representar todas las entidades reales, con objetos software que los representen (que lleven el mismo nombre), decimos que existe un salto pequeño en la representación... es decir, los objetos software son idénticos a las entidades que intentamos representar.
En ciertas ocasiones esto no es tan deseable, y siguiendo con el ejemplo de la venta: supongamos que las ventas se almacenan en una base de datos. Según el patrón "experto en información" el objeto venta es el candidato ideal para almacenarse a sí mismo en la base de datos (BD), ya que dispone de toda la información necesaria (excepto las cuestiones propias de la conexión a la BD). Esto nos lleva a incluir código referente a la conexión dentro de la clase Venta, lo que resultaría en baja cohesión y un elevado acoplamiento. En este tipo de situaciones, es donde se hace necesaria la creación de una clase de objetos que no se corresponda con un objeto de la vida real, sino que sea una "Fabricación Pura". Esta clase, que es una invención, podría llamarse en este caso "ConexionBD" por ejemplo, e incluir todas las funciones necesarias para almacenar datos en la BD. Con esto se logra mayor potencial de reutilización, debido a que, seguramente, otras aplicaciones también necesitan este tipo de funciones.

:Indirección:: la idea de este patrón es asignar las responsabilidades a un objeto intermediario entre otros dos. En el ejemplo de "Fabricación Pura", se creaba una clase intermedia entre la Venta y la Base de Datos, y esto es exactamente un ejemplo de Indirección. El objetivo es desacoplar dos objetos, añadiendo un intermediario. Podemos decir entonces, que muchas fabricaciones puras son creadas debido a la indirección, y la razón de esto es lograr bajo acoplamiento. Muchos patrones de diseño (no incluidos en este artículo) son especializaciones de Indirección.

:Variaciones Protegidas:: el objetivo de este patrón es ayudarnos a lograr que las variaciones de una clase de objetos no afecten a otras, tal como su nombre lo indica, lograr que las variaciones que puedan surgir en una clase estén aisladas o "protegidas". Los principales mecanismos para lograr variaciones protegidas son la indirección y el polimorfismo. El ejemplo anterior, donde se creaba una "Fabricación Pura" (una clase de conexión a la BD), además de ser un ejemplo de indirección, lo es también de Variaciones Protegidas. Esto se debe a que, en el caso de que cambie la forma en que debamos conectarnos a la BD (por ejemplo, dejamos de usar SQL Server para usar Postgre), la venta "no se entera". Es decir, las variaciones que puedan surgir dentro de la clase ConexionDB no afectan a sus clases usuarias.
Resumiendo, el objetivo de las variaciones protegidas es encontrar puntos donde existan variaciones previsibles, y crear una interfaz a su alrededor, de manera que las clases que colaboran no sufran los efectos de las posibles modificaciones.

Comentarios:

Nombre:
Comentario: