13 de noviembre de 2021

Introducción a los patrones de diseño



Con este artículo comienza una nueva serie, que será el inicio de varias otras nuevas series, las cuales hablarán de diversas formas de mejorar la calidad de nuestro código, con el objetivo de hacerlo más mantenible, flexible, entendible, y mejor estructurado.

Con estas series busco brindarte una base formal que te ayude a afrontar problemas complejos y te prepare mejor para desarrollar tu carrera profesional.

En esa serie te explicaré qué son los patrones de diseño y hablaré de los patrones de diseño más comunes, y te mostraré diversas formas o estratégias para implementar cada uno de ellos.

En este primer artículo explicaré rápidamente qué son los patrones de diseño, cómo se clasifican, junto con una lista que se irá actualizando conforme vaya agregando más artículos.

Sin más, vamos al tema.



¿Qué son los patrones de diseño y por qué son importantes?

Cuando nos enfrentamos a un problema, pensamos en distintas soluciones para resolverlo, incluyendo soluciones exitosas que hemos aplicado en el pasado para el mismo o un problema similar.

Lo primero que debes saber cuando tratas de resolver un problema es que es muy probable que alguien más ya haya resuelto ese problema. No solo eso, sino que es probable que muchas personas ya hayan resuelto ese problema, hayan hablado de eso con sus colegas, en reuniones, en la cafetería, en foros, eventos especializados, etc.;que al final, se hayan dado cuenta de que es un problema común, hayan intercambiado distintas experiencias sobre cómo fue resuelto, esas experiencias se hayan documentado, recopilado, que varios otros miembros de la comunidad haya descartado algunas de esas soluciones, hayan probado varias de las soluciones no descartadas, y que al final la comunidad haya decidido cual o cuales son las mejores formas de resolver ese problema; se le haya dado un nombre, se haya clasificado junto con otros problemas similares, se hayan escrito libros, dado conferencias y cursos sobre estas mejores formas de resolver los problemas, se hayan hecho una o dos películas y documentales, etc., etc.

Los patrones de diseño entran dentro de la larga explicación del párrafo anterior (excepto, obviamente, por las películas y documentales; y sí, a mí también me frustra que haya una película sobre emojis, pero no sobre patrones de diseño). La gran ventaja que esto nos da es que no tenemos que empezar a experimentar desde cero una nueva solución, podemos buscar nuestro problema dentro de un catálogo y usar la solución que mejor se adapte a nuestro contexto particular. De esta forma reutilizamos la sabiduría de los que se enfrentaron al problema antes de nosotros.

Para explicarlo de una forma más sencilla: Los patrones de diseño son la abstracción de soluciones a alto nivel, probadas y reutilizables para problemas comunes en programación. El aplicarlos nos evitan el tener todos los problemas que conlleva reinventar la rueda. En resumen: los patrones de diseño son soluciones reutilizables a problemas que ocurren con frecuencia cuando se diseña y desarrolla una aplicación. Cuando digo que son una abstracción de una solución a alto nivel me refiero a que podemos considerarlos unos planos pre-creados que se aplican para solucionar un problema particular; no son piezas de código terminadas que puedan ser aplicadas directamente en nuestra aplicación, sino como plantillas o descripciones que pueden darnos una idea (o la inspiración) de cómo resolver el problema. Es por esto que un mismo patrón aplicado a dos escenarios diferentes resultará en que la implementación del patrón sea diferente.

Los patrones de diseño no han dejado de ser vigentes desde que fueron definidos hace ya muchos años, ya que su uso nos ayuda a escribir código de forma más limpia y modular. Cada patrón de diseño gira al rededor de un concepto fundamental que es la base para lograr un buen diseño: no modificar el código existente. Una vez que modificamos código que ya funciona, existe una alta posibilidad de que introduzcamos nuevos defectos y resultados no deseados. Lo que resulta en ciclos de pruebas más largos y exhaustivos para asegurar que nada se ha roto.

Estos patrones ayudan a minimizar la duplicación de código, prevenir el acoplamiento entre clases y estandarizar la forma de escribir código.Con estos principios en mente se busca escribir código flexible y reutilizable.


Un poco de historia

La historia de los patrones de diseño comienza en 1977, pero no en el ámbito de software, sino en el de la ingeniería civil. En este año Christopher Alexander publica el libro "A Pattern Language Towns, Buildings, Construction". En este libro en lugar de hablar sobre los detalles de la construcción, Alexander se centra en los problemas comunes que enfrenta un trabajo de arquitectura. Estos problemas los explica de la forma más general posible, capturando solo su esencia. Con esto, logró que el libro hable de problemas generales y atemporales. En este libro también habla de las relaciones entre estos problemas.

¿Qué tiene que ver esto con el software? Pues bien, en 1994 un grupo de 4 sujetos: Erich Gamma, Richard Helm, Ralph Johnson, y John Vlissides tomaron esta idea de patrones para poder aplicarla al mundo del desarrollo de software y publicaron uno de los libros más influyentes en la historia de la programación: "Design Patterns: Elements of Reusable Object-Oriented Software". Cariñosamente nos referimos a este grupo de autores con el nombre de “La banda de los 4” (Gang of Four, en inglés) o simplemente GoF.

Este libro presenta un catálogo de 23 problemas que aparecen de forma recurrente cuando construimos aplicaciones usando un lenguaje orientado a objetos; además ofrece una solución general para cada uno de estos problemas. A cada una de estas soluciones, o patrones, les da un nombre y las clasifica dentro de uno de tres tipos. Facilitando con eso la creación de un lenguaje común para todos los programadores.

De acuerdo con los autores, todos los patrones de diseño se basan en los siguientes principios del diseño orientado a objetos:
  • Programar hacia una interface, no a una implementación.
  • Favorecer la composición de objetos frente a la herencia de clases.
  • Determinar qué parte del problema es común y qué parte es variable.
  • Permitir el reemplazo de la parte variable mediante la interfaz común.
Hay que recordar que estos autores no tomaron solo su experiencia para escribir estos patrones. Sino que recopilaron la experiencia de los problemas comunes que observaron en la comunidad y los concentraron en una sola fuente de información.

Los patrones de diseño se clasifican en 3 grupos:
  • Creacionales. Diferentes maneras de crear objetos o grupos de objetos. Sí, todos los objetos en Java se crean usando la palabra reservada new, pero podemos envolver este llamado en otras estructuras que faciliten que otros objetos obtengan una instancia apropiada e inicializada de forma correcta. Esto ayuda a ocultar los detalles de cómo los objetos son creados o inicializados.
  • Estructurales. Diferentes formas de crear la estructura de las clases; por ejemplo, usando herencia y composición para crear objetos grandes y complejos a través de otros más simples.
  • De comportamiento. Formas en las que podemos lograr una mejor interacción entre objetos para lograr un bajo acoplamiento y flexibilidad.
Cado uno de los grupos anteriores se divide en dos diferentes ámbitos: Clases y objetos.

Los patrones de diseño con ámbito de Clase indican la forma en que las clases se relacionan con sus subclases a través de la herencia. En estos patrones los elementos que usará la aplicación se conocen o resuelven en tiempo de compilación; esto quiere decir que no se modifican a lo largo de la ejecución del programa ya que su estructura es estática. Puedes recordar este alcance usando la frase: Los elementos se conocen en tiempo de compilación.

En los patrones de diseño con ámbito de Objeto se solucionan problemas usando instancias de diferentes clases polimórficas que pueden ser intercambiadas en tiempo de ejecución. Esto quiere decir que podemos usar una instancia en la ejecución de la aplicación, y luego cambiarla por una instancia de una clase diferente (que tenga la misma clase base que la anterior) en otro momento; por lo tanto, no todos los elementos se resuelven en tiempo de compilación; la estructura de estos patrones es dinámica en ejecución y se logra a través de la composición. Puedes recordar este alcance usando la frase: Pueden ser cambiandos en tiempo de ejecución.

Existe un patrón que tiene los dos ámbitos; esto quiere decir que dependiendo de cómo lo implementemos podemos usarlo a nivel de clase o a nivel de objeto.

En la lista de patrones que está más abajo podrás ver que la mayoría de los patrones trabajan en el ámbito de los objetos. El entender el álcance o ámbito de un patrón nos ayuda también a tener más elementos para saber si un patrón particular los puede ayudar a no a resolver el problema que estemos enfrentando.

Con esto, logramos tener 6 subclasificaciones:
  • Creacionales
    • Clases
    • Objetos
  • Estructurales
    • Clases
    • Objetos
  • De Comportamiento
    • Clases
    • Objetos

 A continuación, te dejo una lista de los patrones contenidos en este libro. A lo largo de esta serie iremos desarrollando la explicación de cada uno de estos patrones.

ClasificaciónClaseObjeto
Creacionales
Estructurales
  • Adapter (sí, este patrón puede usarse en los dos ámbitos)
  • Bridge
  • Composite
  • Decorator
  • Facade
  • Flyweight
  • Proxy
De comportamiento
  • Chain of Resposibility
  • Commander
  • Iterator
  • Mediator
  • Memento
  • Observer
  • State
  • Strategy
  • Visitor


¿Son estos los únicos patrones que existen? No, cada tecnología, lenguaje de programación, o incluso framework, puede definir sus propios patrones de diseño. En esta serie veremos los patrones más comunes y, dependiendo de la respuesta y necesidades, veré si es necesario que entremos en patrones más específicos. También es importante entender que esos patrones de diseño se usan en aplicaciones que usan el paradigma orientado a objetos. Diferentes paradigmas tienen una serie diferente de patrones.

Si quieres ver una lista más o menos completa de patrones de diseño aplicables a Java, puedes consultarlo en esta liga: https://java-design-patterns.com/patterns/

¿Son estas las únicas formas de clasificar los patrones de diseño? Igual que la respuesta anterior: No. Existen varias formas de clasificarlos; la anterior es la forma más común o popular, pero no es la única. Para da un breve ejemplo, en JavaEE (ahora JakartaEE) los patrones se dividen dependiendo de la capa de la aplicación en donde pueden usarse.

Para dar un ejemplo rápido de lo anterior, en JavaEE las aplicaciones se dividen en 5 capas, y se tiene un catálogo de 21 patrones de diseño que aplican para las tres capas intermedias.

 


También tenemos otros tipos de patrones, como los arquitectónicos (MVC, MVVM, VIPER, etc). Estos tienen un alcance más amplio ya que consideran la forma de organizar las capas de un sistema. Dependiendo del patrón estas capas variarán en número y objetivo.

¿Existe una sola forma de implementar un patrón? A esta altura ya sabes la respuesta: No. Existen diversas formas correctas de implementar un patrón de diseño, a cada una de estas formas se les da el nombre de Estrategia.


Definiendo un patrón

Recordemos que el objetivo de los patrones de diseño es recopilar problemas y sus soluciones; pero es importante entender dos cosas:
  1. No todos los problemas se pueden resolver aplicando algún patrón de diseño. Existen problemas para los cuales no existe, aún, algún patrón de diseño. Esto es muy importante ya que una aplicación no puede ser desarrollada usando solo patrones de diseño. Debe ser una combinación entre soluciones generales y soluciones particulares.
  2. Los patrones de diseño tienen un contexto donde se pueden aplicar y una solución. Si tratamos de aplicar esta solución en un contexto diferente para el cual el patrón fue concebido, es posible que terminemos con más problemas de los que teníamos inicialmente. ¿Esto quiere decir que el patrón no sirve? No, quiere decir que estamos aplicando un patrón para un problema diferente al que resuelve; dicho de otra manera, lo estamos sacando de su contexto de aplicación.
De los dos párrafos anteriores podemos concluir que: Un patrón de diseño representa la relación entre tres elementos: un contexto, un problema y una solución.

Cuando se explica un patrón de diseño, esta explicación normalmente incluye:
  • Descripción
  • Escenario de Uso
  • Solución concreta
  • Las consecuencias de utilizar este patrón
  • Ejemplos de implementación
  • Lista de patrones relacionados


Palabras finales de advertencia sobre el uso (y abuso) de los patrones de diseño

Aunque esto pueda sonar un poco contradictorio después de toda la explicación anterior, hay que recordar que, aunque el uso de los patrones de diseño es una buena práctica que ayuda a solucionar problemas, el abusar de ellos y no aplicarlos de forma correcta puede traer muchos problemas en el futuro.

Incluso existe un término para esas personas que intentan aplicar patrones de diseño en todo momento, aún fuera del contexto en el que funcionan, logrando con esto que no se obtenga ningún valor y abusar del uso de patrones de diseño. Este término es: Pattern Happy.

Este término también se aplica a personas que apenas aprender un patrón tratan de aplicarlo a un proyecto sin haber terminado de entender de todo el patrón y en donde no agrega ningún valor.

Muchas veces se trata a los patrones de diseño como verdades universales. Esto es incorrecto.

Martin Fowler nos advierte desde 1997 en su libro "Analysis Patterns: Reusable Object Models" otros dos puntos importantes que debemos tener en mente en todo momento al utilizar patrones de diseño: 

  • Los patrones son un punto de partida, no un destino.
  • Los modelos no están bien o mal, sino que son más útiles o menos útiles.

 Creo que esto es suficiente para una rápida introducción. En el siguiente artículo comenzaremos la aventura de aprender a usar nuestro primer patrón.


Entradas relacionadas