Los patrones de diseño en el desarrollo de software
El desarrollo de software transcurre por varias etapas desde el inicio hasta el despliegue del sistema. En cada una de ellas es importante definir los elementos aplicando buenas prácticas, logrando la mayor organización y favoreciendo la eficiencia de los resultados.
Una de estas etapas es el diseño del software, y no hago referencia precisamente a los colores o las imágenes, sino a como se organizará el código fuente, los componentes y las funciones, de manera tal que sea posible mantener un balance adecuado entre independencia y comunicación a la vez.
Una herramienta para lograr esto es la utilización de los patrones de diseño, los cuales permiten definir qué métodos debemos crear, cómo hacerlos y cómo colocarlo desde la creación de los diagramas.
A partir de una correcta definición del diseño, es muy probable que la programación sea más organizada.
Existen dos grupos de estos patrones:
- Patrones de asignación de responsabilidades o GRASP como se conocen.
- Patrones GoF.
Patrones GRASP
Patrones GRASP es la denominación común que reciben las buenas prácticas asociadas a la asignación de responsabilidades entre los objetos de un software, surgida de la frase en inglés General Responsibility Assignment Software Patterns.
Se conocen alrededor de 8 de ellos, que les muestro en orden de popularidad:
- Experto
- Creador
- Controlador
- Alta cohesión
- Bajo acoplamiento
- Polimorfismo
- Fabricación pura
- Indirección
- Variaciones protegidas
La existencia de estos patrones está estrechamente relacionada con el paradigma de Programación Orientada a Objetos, donde un objeto representa dentro del código fuente a una entidad real.
Por ejemplo, un auto tiene características particulares como el modelo, el color, el año, entre otros; y realiza acciones como correr, frenar, etc.
El auto entonces, desde el código fuente, se define por un objeto, que posee estos atributos y estas funcionalidades.
Dicho esto, vamos a ver los patrones más de cerca.
👉 También te puede interesar leer: Patrones de arquitectura de software: tipos y características
Ahorra en software
Únete al boletín premium semanal con los mejores lifetime deals y ofertas de software.
Patrón Experto
Su nombre hace honor a su principal objetivo. El patrón experto se encarga de lograr que cada objeto dentro de un software sea creado por la clase que posee todos los elementos para hacerlo.
Garantiza que, aunque otras clases utilicen el mismo objeto, solo la experta tenga la responsabilidad de construirlo.
Si deseamos saber cuántos autos de una marca participan en una carrera, tendríamos una clase Auto y una clase Carrera_Autos.
A pesar de que el objeto auto se utiliza dentro de la clase Carrera_Autos, la que conoce y puede contar cuántos hay de un mismo color es Auto, y es la que construye el objeto, que después será utilizado en varios lugares.
Esto favorece que cada objeto puede cambiar sus atributos o comportamiento, sin que las clases relacionadas deban cambiar drásticamente.
La creación del objeto se realiza dentro de sí mismo, característica que se conoce como encapsulamiento.
Patrón Creador
Este patrón es similar al anterior, pero su misión está enfocada en asignar la responsabilidad correcta para crear instancias de un objeto, o dicho claramente, realizar las llamadas a los objetos cuando van a ser utilizados.
Para ser una clase creadora debe cumplirse que:
- es una clase experta o;
- utiliza directamente el objeto o;
- contiene la clase donde se construye el objeto.
Siguiendo con el ejemplo de los autos, la responsabilidad en este caso sería al revés. La clase Carrera_Auto necesita conocer cuántos autos hay de un color en una carrera.
En este caso, debe llamar al objeto Auto para pedirle el dato del color, y contar cuantos objetos con esas características se crean.
Patrón Controlador
Este tipo de patrón es ampliamente utilizado por la arquitectura MVC, siguiendo el mismo objetivo de separar la presentación del negocio, en el desarrollo de software.
En este caso la idea es crear una o varias clases que se encarguen de tomar los datos que el usuario inserta en la vista y enviar la información a las clases donde se utiliza.
Mientras más controladores existan más fácil es el mantenimiento del código fuente y la reutilización.
Patrones Alta Cohesión y Bajo acoplamiento
Aunque son patrones separados, su aplicación en el diseño de un software es simultánea, pues la existencia de uno, implica al otro.
Lo más importante de ellos es conocer que significan sus nombres:
- Alta Cohesión se refiere a la medida en que una clase realiza solo la tarea para la cual fue diseñada. Mientras más especializada sea una clase, más alta será lo cohesión.
- Bajo acoplamiento significa que la relación que existe entre las clases es simple, o muy poca.
Al existir una alta cohesión, el acoplamiento disminuye, pues cada clase incluye poco o nada de código fuente externo. Y de manera similar ocurre al revés.
👉 También te puede interesar leer: WebPack: una mirada al empaquetado de aplicaciones en la web
Patrón Polimorfismo
En el lenguaje popular el polimorfismo es la capacidad de comportarse de diferentes maneras o tener disímiles aspectos.
Este concepto general se aplica totalmente en la programación orientada a objetos (POO), y el patrón con este nombre propone diseñar las clases de forma que un mismo objeto pueda comportarse diferente según la necesidad del sistema.
A partir del mismo ejemplo, tendríamos que Auto es la clase padre, y de ella heredan Auto_Lujo y Auto_Deportivo.
En ese caso un Auto, como objeto, tiene la posibilidad de cambiar su comportamiento dependiendo de la información que reciba de una clase externa.
El patrón entonces permite que las clases u objetos estén planteados de forma tal que se garantice que se utilice el polimorfismo solo en los tipos de un objeto que necesitan cambiar, y no a todos los que heredan de un mismo padre.
Patrón Fabricación pura
Este patrón es, probablemente, el más sencillo de explicar en los GRASP.
Simplemente es la solución para poder incluir aquellos métodos o funcionalidades que si se definieran en clases específicas afectarían el acoplamiento, la cohesión y la reutilización del código fuente.
Entonces es necesario inventar una clase independiente, que los contenga y a la cual acceder para poder utilizar dichos métodos.
Normalmente, estas clases no están relacionadas a ningún objeto o entidad real en el sistema.
Patrón Indirección
Aunque desde otro punto de vista, la opción de aplicar el patrón Indirección se recomienda cuando es complejo definir que objeto debe tener cierta responsabilidad.
En ese caso, se crea una clase intermedia que conecta a los objetos en cuestión. La diferencia con la Fabricación pura es que esta clase sí representa una entidad real en el dominio del sistema.
Sería algo como:
- Si deseamos comprar un auto en el concesionario, podemos hacerlo mediante diferentes formas de pago. ¿Dónde definimos estas formas, en el comprador o en la tienda?
- Pues se hace una clase Forma_Pago, donde se modifique mediante qué vía se va a pagar el auto que se desea comprar.
Utilizando este patrón, se obtiene como ventaja, la reducción del acoplamiento en un diagrama de clases de diseño, y por lo tanto en el código fuente de un software.
Patrón Variaciones Protegidas
Este patrón no es el último por casualidad. Se reconoce como un patrón GRASP avanzado y funciona como una alternativa para evitar que la ocurrencia de cambios en un sistema lo afecte de manera radical.
Su implementación se realiza mediante una Interfaz que, aplicando el polimorfismo, permite que los objetos se comporten de manera diferente en relación con los cambios que ocurran y su repercusión en el software sea mínima.
La característica fundamental que diferencia a este grupo de patrones de otras categorías dentro del diseño de software, es que está enfocado a proponer cómo debe quedar el software en materia de organización de sus clases y componentes.
Existen otros patrones de diseño, que influyen en la programación a partir de los diagramas y que se conocen como patrones GoF.
Patrones GoF
El término GoF no está asociado a los tipos de patrones que agrupa, sino a sus creadores Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides.
El grupo se denominó Pandilla de los Cuatro y este sobrenombre, en inglés Gang of Four, dio paso al nombre GoF.
Estos patrones suman 24, y se identifican por su tipología:
- Creacionales: 5
- Estructurales: 8
- De comportamiento: 11
Fábrica abstracta (Abstract Factory)
La misión de este patrón es lograr agrupar objetos de un mismo tipo como una familia, creando tantas familias como tipos de objetos existan.
Su importancia radica en que esta clasificación por familias es transparente para los objetos que las componen, y, aun así, gracias a la fábrica abstracta, no se mezclan con los de familias deferentes.
Constructor virtual (Builder)
En el desarrollo de un software existen objetos que son más complejo de crear, como, por ejemplo, aquellos cuyos atributos dependen de la creación de objetos más específicos.
Este proceso se realiza mediante un Constructor virtual, que incluye como patrón de programación las características de varios objetos complejos, que llaman a esta clase para obtener la información que necesitan en el momento en que se realizan llamadas sobre ellos.
Método de fabricación (Factory Method)
La lógica de este patrón es similar al de Fábrica abstracta. El método de fabricación lo que permite es crear subtipos de un mismo objeto, manteniendo igualmente la transparencia para el resto del sistema.
Cuando el objeto es llamado, mediante este patrón, se selecciona el subtipo que contiene la información precisa para la llamada realizada.
Prototipo (Prototype)
Existen sistemas que tienen una lógica imprecisa, o realizan funciones en tiempo real. Cuando esto sucede, se debe dar la oportunidad de seleccionar el mismo objeto para cumplir con diferentes objetivos.
El sistema debe estar preparado para tomar la decisión en el momento y lograr la disponibilidad de la información que contiene ese objeto puntualmente.
El proceso entonces sería clonar el objeto para modificarlo en tiempo real sin afectar el original mediante la creación de prototipos. En ese caso se utiliza el patrón del mismo nombre.
Instancia única (Singleton)
Similar al caso anterior, el nombre de este patrón muestra su objetivo de manera directa.
La Instancia única garantiza que se realiza una sola llamada a una clase, luego esa llamada se declara global para el sistema, y a ella se comunican el resto de las clases que necesitan la información del objeto.
Adaptador o Envoltorio (Adapter o Wrapper)
El objetivo de este patrón es permitir el acceso a clases distintas, pero que deben trabajar juntas para brindar la información necesaria.
El mecanismo es crear un interfaz que funciona como un adaptador, mediante la cual se unan las clases en cuestión, y se muestre más sencillo al usuario.
Puente (Bridge)
A través de este patrón se crea un enlace entre las clases abstractas o interfaces, con las clases que implementan su funcionamiento.
De esta forma, un mismo objeto puede compartirse para varias implementaciones, sin afectar su funcionamiento original.
Objeto compuesto (Composite)
Composite aplica al problema que soluciona la recursividad. Es decir, permite que un objeto se componga de otros objetos o los incluya, selecciona para cada caso cuales de ellos se fusionarán.
Así un macroObjeto, se divide en partes más pequeñas e individuales, que se juntan indistintamente según la función que deben realizar.
Decorador (Decorator)
Este tipo de patrón se enfoca más en la creación de funcionalidades de manera dinámica, evitando que se deban crear nuevas clases hijas de una clase padre cada vez que surja una nueva función.
Por el contrario, la opción que se brinda como solución se sobrescribir una clase cada vez que esta cambie, añadiendo el método nuevo.
Fachada (Facade)
En sistemas de gran tamaño, y mayor complejidad, es habitual crear múltiples interfaces para acceder a diferentes objetos, resultado también de la utilización de otros patrones.
La idea de Fachada es crear una interfaz general para acceder a otras interfaces dentro del programa. De esta forma se mejora la accesibilidad y la organización del código fuente.
Peso ligero (Flyweight)
La sobrecarga de objetos en un sistema, generalmente provoca un mayor consumo de recursos. Y es normal que ocurra cuando se crean múltiples llamadas a objetos similares, que contienen la misma información.
Aplicando este patrón, se eliminan dichos objetos, aligerando el sistema y mejorando su rendimiento.
Proxy
Este tipo de patrón se utiliza para redireccionar llamadas a los objetos, filtrando aquellas características que no son necesarias para obtener el resultado que se espera.
Similar a otros patrones, genera una clase intermediaria que maneja como llegar al objeto.
Módulo
Se conoce como un patrón general que agrupa elementos como clases, instancias únicas o métodos, que tienen características comunes.
Con esto se logra que sean más fácil de encontrar, comunicar y mezclar, si se necesita información de varios de ellos.
Cadena de responsabilidad (Chain of Responsibility)
Este tipo de patrón inicia a los patrones de comportamiento, y su función es encaminar las peticiones que se realizan a un objeto para que realice acción para el que fue construido.
Orden (Command)
La característica de este patrón es el encapsulamiento. Mediante este la información de una funcionalidad deja de mostrarse y aun así es posible ser ejecutada.
Intérprete (Interpreter)
Este patrón es muy utilizado en el desarrollo de IDEs de programación.
Mediante él se determina que gramática aplicar en correspondencia con el lenguaje en que se escribe y se brindan las herramientas para poder procesar dicho lenguaje.
Iterador (Iterator)
A pesar de la complejidad que un objeto pueda tener, este patrón favorece que se recorra cuantas veces sea necesario con el objetivo de obtener un resultado satisfactorio.
Mediador (Mediator)
El resultado de una acción en un sistema suele verse sencilla, sin embargo, por detrás es necesario que varios objetos diferentes trabajen de conjunto.
El patrón mediador se encarga de crear un objeto cuyo único objetivo es coordinar a otros objetos, que, aunque están separados, la comunicación entre ellos ejecuta la acción solicitada.
Recuerdo (Memento)
Es sencillamente guardar estados del sistema para permitir volver a ellos si es necesario, disminuyendo el tiempo de ejecución.
Observador (Observer)
Cuando varios objetos están relacionados, es importante que todos se modifiquen para mantener la sincronía.
Al implementarse un observador, se mantiene la alerta sobre cambios en objetos con dependencias entre ellos de manera que al ocurrir un cambio, se conozca y se ejecuten acciones para que el resto de los objetos también cambien.
Estado (State)
Si un objeto cambia alguna característica interna, debe permitirse que cambie su estado de cara a otros objetos o interfaces.
La programación de este patrón favorece que el cambio de estado se ejecute, se notifique y se visualice.
Estrategia (Strategy)
Técnicamente es crear varios métodos para solucionar un problema que se puede comportar de manera similar.
Siguiendo este patrón se implementan varias funcionalidades con un mismo objetivo pero diferente comportamiento y se selecciona en tiempo de ejecución, el cual se utiliza según el contexto.
Método plantilla (Template Method)
Crea un algoritmo base, o un subprograma, que funcione como un esqueleto.
En la medida que surjan nuevas necesidades, se agregan los cambios dentro de este, sin afectar la estructura central.
Visitante (Visitor)
En la aplicación de árboles de clases o en las herencias, el patrón Visitante juega un papel importante, pues mediante este es posible realizar cambios en las operaciones de una clase hija sin afectar la organización de la jerarquía.
Es decir, se recorren las clases hasta encontrar la que debe ser modificada, se accede a ella y se realiza la modificación.