El lenguaje común de los patrones de diseño GoF

0
1529
Patrones
(CC BY-ND 2.0) https://www.flickr.com/photos/paulmccoubrie/6792412657/

Hace unos días, pregunté en una encuesta sobre prácticas de nombrado de las clases que participan en un patrón de diseño GoF.
Aparte de esto, la encuesta también preguntaba sobre la utilidad de estos patrones y su conocimiento.
Mi intención era comparar mis experiencias con las de otros compañeros y los resultados confirmaron lo que intuía. Aun así, me sorprendieron.

Introducción

Conversaciones hipotéticas pero totalmente factibles…

Durante un desarrollo:

  • Desarrollador 1: Necesito securizar mi API REST con HTTP BASIC en un microservicio Spring Boot. ¿Cómo lo hago?
  • Desarrollador 2: Crea una clase anotada con @Configuration y que extienda WebSecurityConfigurerAdapter.
    Sobreescribe el método configure HttpSecurity y usa el API fluida para autorizar todas las peticiones con HTTP BASIC.
    En el constructor inyéctate AuthenticationManagerBuilder para definir los usuarios y contraseñas.
  • Desarrollador 1: Ah, entendido, gracias.

 

En una revisión de código:

  • Desarrollador 1: ¿Podrías echarle un vistazo a la forma en que he solucionado este problema?
  • Desarrollador 2: A ver… No está mal, pero yo hubiera aplicado el patrón Decorator.
  • Desarrollador 1: ¿Lo qué? 😵

 

La primera conversación incluye muchos más términos técnicos que la segunda, y sin embargo es mucho más probable que la primera se entienda bien y la segunda no.
¿Por qué suele ocurrir esto?

El lenguaje común

Una característica importante de cualquier gremio profesional es la existencia de un lenguaje técnico común con el que sus miembros pueden comunicarse de manera eficiente.
El gremio de los desarrolladores de software por supuesto tiene uno propio que, además, es tremendamente amplio y está en constante revisión.

Las dos conversaciones anteriores sonarían a chino a cualquier persona fuera de este gremio, pero lo curioso es que la segunda conversación tampoco sería entendida por bastante gente dentro del gremio.

Se me ocurren dos razones para esto.

La primera es que los términos de la primera conversación se refieren a cosas específicas, elementos concretos que son fáciles de memorizar y aplicar. Sin embargo, el concepto detrás de un patrón de diseño es mucho más complejo y abstracto y, por lo tanto, más difícil de entender, memorizar y aplicar.

La segunda razón es que las siglas y términos relacionados con una tecnología o framework concreto gustan mucho porque tienen aplicación inmediata a lo que programamos, es decir, aparentan ser muy útiles.
Por otro lado, para muchos desarrolladores, un patrón de diseño no parece tener una utilidad inmediata.

La utilidad de los patrones de diseño

Estamos de acuerdo en que nuestro objetivo primordial es aportar valor a negocio desarrollando y entregando cuanto antes la funcionalidad requerida.
Esto lo podemos lograr con metodologías ágiles, Extreme Programming, etc…

También es algo aceptado que hay que acercar el lenguaje de la solución (el código, la implementación) al lenguaje del problema (el dominio del negocio).
Las técnicas DDD (Domain-Driven Design) intentan optimizar este acercamiento.

Si los patrones de diseño GoF no implementan per se ninguna funcionalidad de negocio y además son conceptos técnicos puros sin ninguna relación con el domino de negocio, ¿qué pintan aquí? ¿Para qué los necesitamos? ¿Qué aportan?

¡La respuesta la tenemos en los propios patrones GoF!

> Los patrones de diseño son soluciones reutilizables a problemas comunes de diseño de software.
> Se clasifican según su propósito en: creacionales, estructurales y de comportamiento.

¡Un momento! Mi lenguaje de programación favorito ya me proporciona mecanismos para crear componentes, organizarlos en estructuras arbitrariamente complejas e implementar colaboraciones entre ellos.

Cierto, pero el lenguaje programación está a un nivel de abstracción más bajo, sus herramientas son más «primitivas».
Al menos esto es así en los lenguajes más populares, aunque hay excepciones: algunos proporcionan construcciones de más alto nivel que solucionan los mismos problemas que algunos patrones y otros simplemente evitan el problema que soluciona algún patrón.

Otra cuestión es que nadie programa «a pelo». Siempre añadimos un framework y librerías de utilidad que nos solucionan problemas de más alto nivel como los que abordan los patrones. ¿Verdad?

Sí, es verdad, ¿pero cómo te crees que están implementados ese framework o esas librerías de utilidad?
Cojamos Spring, el framework más universal en Java.
Resulta que Spring es un escaparate de los patrones de diseño GoF. Mires donde mires, ahí están, muchas veces con sus nombres explícitos (más sobre esto después): Singleton, Template Method, Abstract Factory, Proxy, etc…

Tema zanjado entonces. Ya se encargan otros de aplicar los patrones donde se necesitan, ¿no?
Pues no, no del todo.

Soluciones técnicas a problemas del dominio

Por mucho que aporten el lenguaje y el framework, sigue sin ser suficiente para solucionar todos los problemas de diseño e implementación que se nos plantean.

En nuestro código, además de resolver el problema que tiene negocio, en algunas ocasiones tendremos que lidiar con problemas puros de diseño de software.

Algunos ejemplos típicos que encontramos entre la lógica de aplicación:

  1. Construir un objeto de forma que cumpla los invariantes de negocio (patrón Builder).
  2. Modelar una estructura de entidades con relaciones jerárquicas (patrón Composite).
  3. Aplicar diferentes políticas en un proceso (patrón Strategy).
  4. Ejecutar un procedimiento en diferentes pasos y condicionalmente según el caso (patrones Chain of responsibility o Decorator).
  5. Evitar contaminar nuestra lógica de negocio pura con detalles de comunicación con un proveedor (patrón Facade).
  6. Cambiar el comportamiento según el estado de una entidad (patrón State).

En todos estos ejemplos se ve claramente que hay un entrelazamiento entre la solución del dominio y la solución técnica.
Tendremos conceptos del dominio de negocio mezclados con conceptos puramente técnicos.

En muchas ocasiones, intentar solucionar estos problemas sin aplicar patrones da lugar a una complejidad difícil de asimilar.
Todos hemos visto código que es básicamente un mejunje incomprensible de código que mezcla la lógica de negocio con la técnica de programación ofrecida por el lenguaje o framework de turno: cadenas interminables de «ifs», comportamiento repartido entre clases con un alto acoplamiento, niveles de abstracción incoherentes, lógica de comunicación con un proveedor mezclada con lógica de negocio…

Los patrones evitan esta complejidad accidental porque nos permiten distinguir claramente la parte de la solución del dominio de la parte que corresponde a la solución técnica.
Dicho de otro modo, son capaces de mantener juntos pero separados el lenguaje del dominio y el lenguaje técnico.
¡Y además con unos conceptos abstractos potentes pero muy bien definidos y que todos compartimos!

Nombrando las clases de los patrones

Una vez hemos decidido que un patrón de diseño determinado es la solución adecuada a un problema concreto, ¿cómo nombramos las clases que implementan el patrón?

Como dice Kent Beck, es muy importante que el código refleje nuestra intención.
Así que la intención de utilizar el patrón debe hacerse explícita en el código.

En el patrón, a cada clase que participa se la asigna un nombre que corresponde con su rol, cogiendo el papel más «protagonista» como nombre del propio patrón.
De hecho, estos son los elementos que conforman el lenguaje común de los patrones GoF: el memento, el command, el builder, etc…
Por lo tanto, tiene sentido que usemos ese rol en el nombre de nuestras clases que implementan el patrón.

Pero no olvidemos que esta solución la aplicamos para un problema del dominio de negocio, así que el lenguaje del dominio también debe estar presente.

La recomendación es entonces usar un nombre que resulte de la unión de ambos conceptos siempre que sea posible.
¿Cómo? Usando el rol de la clase como núcleo y su significado en el dominio como complemento preposicional.
¿¿¿Cómo??? Mejor con ejemplos:

  • Command de administración de usuarios: UserAdministrationCommand
  • Decorator para registrar trazas de comandos: CommandTracingDecorator
  • Observer del progreso de registro: RegistrationProgressObserver
  • Builder de productos: ProductBuilder
  • Strategy de selección de candidatos para pruebas: TestCandidateSelectionStrategy
  • Memento del cambio realizado por el usuario: UserChangeMemento
  • Composite de áreas de negocio: BusinessAreaComposite
  • State del flujo de publicación de un artículo: ContentPublicationFlowState

 

Como toda recomendación, hay que valorar su uso en cada caso particular, y habrá muchos casos en los que no tenga sentido.
Estos casos suelen darse cuando no hay manera de encajar el lenguaje del dominio con el técnico.
Como el lenguaje del dominio siempre debe prevalecer, simplemente reflejaremos el uso del patrón con un comentario en la clase «protagonista».

Conclusión

Creo que ha quedado plenamente justificada la utilidad de los patrones de diseño GoF como lenguaje común para las soluciones técnicas dentro de nuestra aplicación.

Y del mismo modo que promovemos mantener el lenguaje del dominio dentro de nuestro código, también deberíamos mantener el lenguaje común técnico usado en el código.

El objetivo en ambos casos es el mismo: facilitar la comunicación y el entendimiento entre todos los miembros del equipo, consiguiendo por lo tanto que nuestra aplicación sea más fácil de mantener.

Otro detalle interesante es que ambos lenguajes son independientes de la tecnología concreta que estemos utilizando y por supuesto aplican igual a front o a back.

¿Y qué pasa con los otros patrones?

Además de GoF, existen otros conjuntos de patrones entre los que destacan GRASP, SOLID y patrones de arquitectura.

Obviamente, estos también deben formar parte del lenguaje técnico de cualquier profesional del gremio.
Por otro lado, estos patrones son más principios generales de diseño, así que su influencia en el código es ubicua.
De hecho, los GoF se pueden ver como una implementación de los patrones SOLID.

Recomendaciones

Dos recomendaciones para terminar:

  1. Si no conoces o no entiendes los patrones de diseño, ¡ponte con ello!
    Estúdialos y, sobre todo, úsalos cuando sea apropiado.
    Recuerda que son soluciones a problemas de diseño, así que el momento de acordarte de ellos es justo antes de ponerte a picar código. Piensa si esa lógica que vas a implementar tendría más sentido estructurarla con un patrón.
    Otras veces el patrón surge delante de nuestras narices, sobre todo en la fase blue/refactor de TDD.
  2. Si no identificas el patrón en el nombre de tus clases, lo estás ocultando y quizás creando confusión para los futuros mantenedores de ese código.
    Es mejor declarar explícitamente el rol que tiene una clase en un patrón, a no ser que esto entre en conflicto con el uso del lenguaje del domino. En este último caso yo daría prioridad a la terminología del dominio de negocio, pero dejaría un comentario explicando el uso del patrón.

Referencias

  1. Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley
  2. Design Patterns, Source Making

Dejar respuesta

Please enter your comment!
Please enter your name here