Saltar al contenido principal

patrones de diseño de software en Java

un icono de engranaje azul sobre un fondo azulun icono de engranaje azul sobre un fondo azul
Erick_Romero.jpeg
autorIngeniero de Software, EPAM Anywhere

Soy un desarrollador Java con 7+ años de experiencia y altas capacidades en todos los aspectos del ecosistema Java como Spring Boot y GCP y diseñando soluciones para las personas. También soy un amante del código limpio y los algoritmos.

Soy un desarrollador Java con 7+ años de experiencia y altas capacidades en todos los aspectos del ecosistema Java como Spring Boot y GCP y diseñando soluciones para las personas. También soy un amante del código limpio y los algoritmos.

Los patrones de diseños de software han ido creciendo exponencialmente durante los años debido al crecimiento de la construcción de software más mantenible, escalable y flexible en términos de cambios. Por ende, estos patrone de diseño han tenido gran acogida y su uso se ha maximizado siendo así un requerimiento para muchos desarrolladores de software conocer e implementar cuando se necesiten.

Uno de los ejemplos es que ya puedes sustituir el patrón Strategy con una simple función lambda por lo cual es importante conocerlos y estar a la vanguardia de los cambios en la tecnología.

Lo que deseo con este articulo acera de patrones de diseño es crear conciencia de la necesidad de conocer y tener la habilidad para elegir el que más se adecua a tu necesidad.

¿Qué es un patrón de diseño?

Es una solución a un problema recurrente dentro del desarrollo de software y se puede ver cuando el desarrollador repite una solución una y otra vez dentro de varios proyectos, entonces alguien les pone un nombre y explica la solución que el patrón va a resolver y así nacen los patrones de diseño.

Los patrones de diseño en total son 23 y se clasifican en 3 grupos, los creacionales, estructurales y comportamiento, y cada uno tiene sus diferentes características, ventajas y desventajas.

Los patrones de diseño son comúnmente encantados en lenguajes de programación orientados a objetos como Java, C++, C, C#, Typescript y entre otros.

Ya habiendo hablado un poco lo que son y que tipos de patrones de diseño existen, vamos a enfocarnos en algunos ejemplos en Java.

En un pasado antes que existiera la programación orientada objetos se utilizaba en paradigma de programación secuencial donde todo estaba en la misma clase y nos limitaba el poder modificar fácilmente el software, cuando nació la programación orientada a objetos y tuvo mucha acogida, también empezó el crecimiento de los patrones de diseño y mucho más en Java.

Vamos a repasar los patrones de diseño más usados en Java para mi opinión por tipo y en que tecnología y juegos los podemos encontrar y así conocer así su importancia dentro del desarrollo de software Java.

  • Creacionales: Factory, Builder, Singleton, Abstract Factory.

Nota: No voy a entrar en discusión si singleton es una anti-patron, pero a veces necesitas una sola instancia de un objeto y también estas cumpliendo con la idea objetos y pienso que con 1 es suficiente, ¿no? .

  • Estructurales: Proxy, Adapter, Decorador.
  • Comportamiento: Command, Strategy, Observer.

Bueno ya una vez escogido los patrones de diseño más usados en Java, vamos con las implementaciones y ejemplos.

encuentra tu trabajo ideal
Solo envíanos tu CV y nuestros reclutadores te contactarán con una opción a la medida
aplica ahora
icono de lupa

Factory

Este patrón nos permite crear diferentes objetos usando la palabra new, pero no directamente en la clase que lo necesita si no desde un método que podríamos llamar fabrica y esta a su vez nos devolverá el objeto solicitado, pero desde otra clase.

A estos objetos devueltos se les denomina productos.

Vale mencionar que no hay problema con crear los objetos usando la palabra new, pero crearemos una gran dependencia entre las clases y siempre que requiramos crear una clase que depende de otras necesitaremos también proveer el objeto dependiente.

El framework de Java más conocido que utiliza el patrón de diseño Factory es Spring con su IOC container.

Mario Bros y otros miles de videos juegos utilizan Factory para la creación de sus enemigos, obstáculos y armas.

En este ejemplo vamos a crear una Factory para crear un sorteo de futbol con distintos equipos ya sea random, los más débiles e inclusive los del mismo país.

Primero creamos nuestra interfaz TeamFactory que va a compartir el método común de las implementaciones.

Luego de eso vamos a crear las implementaciones las cuales son TeamSameCountryFactory, RandomWeakTeamFactory, RandomTeamFactory.

Y por último bajo la clase principal instanciaremos la clase Factory que necesitamos instancias y a su vez hacemos intercambiables los objetos usando polimorfismo.

Y así creamos nuestro sorteo de futbol retornando los equipos bajo ciertos criterios que deseamos.

Les dejo el repositorio con toda la implementación: Factory implementación.

Builder

Es un patrón que se centra en la creación de un objeto complejo paso a paso.

Nos evita crear por cada implementación personalizada de un objeto un constructor que satisfaga tal objeto y solo utilizáramos los parámetros que necesitamos para crearlos.

Lo podemos ver en cualquier aplicación que requiere tal comportamiento.

En este ejemplo vamos a crear diferentes objetos de tipo Car donde tenemos 3 parámetros que son requeridos y 3 que no lo son y vamos a crear objetos de tipo car sin necesidad de estar declarando los diferentes constructores para cada tipo de objeto car.

Creamos la clase Car:

La clase es más extensa entonces los invito a revisar la rama con la respectiva implementación

Luego de ello creamos una clase static con la cual vamos a setear los parámetros que son opcionales y debe retornar el mismo objeto Builder después de setear los atributos opcionales.

Aquí estamos creando los diferentes que objetos que deseamos y como queramos:

Cabe recordad que existen librería que hacen esto por nosotros y es el caso de Lombok y que los invito a usar y juzgar: Builder implementación.

Singleton

Es un patrón de diseño que se centra en crear objetos, pero solo una sola instancia del objeto deseado.

Este patrón nos permite asegúranos que tendremos la misma instancia siempre.

Se puede ver Spring framework con los Beans y componentes, la idea es que exista una sola instancia del componente y sea devuelto las veces que se solicite por otras clases.

También se lo puede ver en una conexión a una base de datos o una servido websockets.

En este ejemplo vamos a evitar crear más de dos instancias de un servidor websockets para un chat y evitar que se cree un nuevo objeto del cada vez que se conecte un nuevo usuario.

Creamos nuestra clase servidor y declaramos el constructor privado, pero vamos a acceder a ese constructor a través de una variable que nos va a validar si ya hay o no una nueva instancia de un servidor.

Y vamos a asegúranos que cada vez que se conecte un usuario se le retorna la misma instancia del servidor.

Esta fue la implementación del patrón Singleton: Singleton implementación.

Abstract Factory

Este patrón es una mejora del Factory y lo que busca es crear familias de objetos relacionados o dependientes sin especificar su clase concreta.

Este ejemplo lo podemos ver en Mario Maker donde nos permiten instanciar diferentes niveles y combinando los elementos de Nintendo DS o Gameboy permitiéndonos crear los respectivos métodos que se necesiten para crearlos y así las implementaciones concretas son las que implementan los métodos.

En este ejemplo vamos a simular los temas dark y white de WhatsApp donde vamos a tener unos estilos visuales y objetos en cierros tipos de colores y que a su ven se relacionan entre sí.

Primero crearemos una interfaz llamada AbstractFactory la cual va a tener los métodos pertinentes que cada implementación va a tener y esa se encargara de instanciar las correspondientes clases.

Y luego creamos las pertinentes implementaciones de cada tipo de tema en este caso black y white e instanciar las clases concretas pertinentes a cada theme.

Y también se crean las clases pertinentes para las implementaciones como WhiteTheme, BlackTheme, Theme y las cuales cada Factory instanciaran, luego de eso instanciamos la factory deseada par así simular el cambio de los temas en modo de ejecución.

Resultado obtenido al llamar las implementaciones:

Y de esa manera podemos implementar el patrón Abstract Factory.

Pueden conocer un poco más de la implementación en el link de GitHub de abajo: Implementación Abstract Factory.

Ya conocimos un poco de los patrones de diseño creacionales ahora vamos a aprender los estructurales.

Proxy

Es un patrón de diseño que busca controlar el acceso a un objeto y permitiéndote hacer algo antes o después que el objeto original reciba una solicitud o el llamado desde otra clase.

Se puede ver el Spring framework con el uso de la programación orientada a aspectos, el módulo transaccional para el rollback y commit de las transacciones dentro de la aplicación, spring security module también lo usa y finalmente su sistema de logging.

También es una buena forma de añadir logging o tracking a un objeto en sistemas antiguos.

Para este ejemplo vamos a añadir logging a un código legacy y no queremos modificarlo ya que la idea es que un nuestro sistema este abierto para añadirle funcionalidad, pero cerrando para modificación.

Primero creamos una interfaz llamada Logger:

Luego su implementación normal con un método multiply del cual vamos a llamar en tiempo de compilación

Lugo la implementación de una clase llamada LoggerProxy la cual va a llamar el método multiply de las clases LoggerImpl después de haber realizado una tarea previa.

Entonces lo que haremos es delegarle el llamado del método multiply al LoggerProxy y así poder realizar ciertas acciones después o antes de llamarlo con el proxy como por ejemplo loguear la hora del llamado del método.

Al ejecutar la aplicación obtenemos este resultado

Aquí concluimos con el patrón diseño proxy: Implementación Proxy.

Adapter

Este patrón de diseño te permite la comunicación entre objetos no compatibles a través de interfaces, por ejemplo, un traductor, cuando dos personas no hablan el mismo idioma, generalmente un intérprete los acompaña y traduce la conversación, lo que permite la comunicación entre ellos.

También lo podemos ver en los servicios RESTful, tu estas programando en Python y Java y deseas compartir alguna información, entonces usan los servicios REST que funcionan como un puente y aumenta la interoperabilidad entre dos servicios externos.

Para este ejemplo vamos a crear un dispositivo que traduzca de un idioma a otro, por ejemplo, de español a inglés y a su vez podemos incrementar más los idiomas a traducir a través de diferentes implementaciones del Adapter.

Primero creamos nuestra interfaz interpreter con el siguiente método para retornar el nombre del lenguaje de origen.

Luego de ello creamos una interfaz con la función de Adapter:

Realizamos las pertinentes implementaciones usando composition:

Finalmente ejecutamos el código y lo que hacemos es enviar el idomas con el cual vamos a hacer la traducción al idioma deseado.

Y este seria el resultado final:

También podemos utilizar este patrón de diseño si queremos convertir imágenes, audios o datos a diferentes formatos: Adapter implementación.

Decorador

Este patrón permite añadir funcionalidades extras a objetos a través de objetos que encapsulan ciertas características que permiten evolucionar el estado del objeto agregándole nuevas características.

Su implementación se ve en Pokémon donde contamos con machismos enemigos y diferentes poderes y nos permite añadir características para evolucionarlos fácilmente (pokémon pikachu – raichu; evolcionar los pokemones o con Dragon ball).

Para este ejemplo vamos a usar un ejemplo de la vida real y me refgiero a de los carros que se van actualizando poco a poco bajo una base y se le añadir nuevas cosas y características.

En este caso yo amo los SUV de Toyota en Colombia y más propiamente la Toyota Lund Cruiser Prado. Así que vamos a por ello.

Primero crearemos muestra landcruiser base de la cual vamos a partir todos:

Implementamos ciertos métodos que todas las landcruiser comparten de la siguiente interfaz:

Luego de esos creamos una clase abstracta llamada landCruiserDecorator el cual nos permite utilizar internamente una Toyota ya decorada con nuevas características.

Con este decorador abstracto podemos empezar a crear otros e ir añadiendo nuevas características a nuestra landcruiser.

Ahora vamos a crear otro decorador del cual va a tener el decorador landcruier previo y va a tener un nuevo tipo de cilindraje en el motor.

Y así podemos ir añadiendo más decoradores.

Al final ejecutamos nuestra clase principal y podemos ver que estamos iniciando con LandcruiserTx que es el modelo base y que desde él se crean las nuevas versiones de esta misma línea.

Luego se le pasa a la tototaTxtlDecorator un objeto de tipo LandcruiserTx para agregar nuevos comportamientos y así sucesivamente.

Y este resultado final:

Ahora con esto podemos modificar el comportamiento de métodos como getEngine o name() o añadirle nuevos métodos sin necesidad de tener que implementar una clase de Toyota por cada posible combinación de decoradores.

Y como siempre dejo la implementación para que le echen un vistazo: Decorator implementación.

Ahora vamos a ver los ejemplos de los patrones de diseño de comportamiento.

Command

Este patrón nos permite ejecutar ciertas operaciones sin conocer los detalles de la implementación de esta. Por ejemplo, él envió de un request al servidor, pero tú solicitud, GUI o frontend no conoce que clase ejecutará ese request ni que acción hará.

Se puede ver en una interfaz gráfica donde no se quiere crear una comunicación directa con el servidor si no que a través de los comandos se pueda hacer esa comunicación, así generamos menos dependencia entre la GUI y el servidor.

Para nuestro ejemplo vamos a crear un pequeño software para poder retirar dinero de una cuenta, pero esto lo va a ser un objeto en concreto a través de una interfaz así nuestro sistema no tiene ninguna relación con quien crea la petición.

También nosotros podríamos crear una serie de comandos a ejecutar en un sistema a través de una cola.

Primero crearemos nuestro reciver o request que es objeto que realiza la petición de esta manera.

Una vez creado el invoker, creamos la interfaz que llamaremos IOperation y es el comando que nos va a permitir una implementación ilimitada de este metodo.

Ahora implementamos al interfaz IOperación y se le da la respectiva implementación al metodo execute():

Luego de hacer esa implementación creamos una clase llamada invoker que es el que va a ejecutar las ordenes, en este caso los diferentes comandos que se pueda generar, por ejemplo, un cliente quiere depositar dinero u otro retirar y todo se puede almacenar en una cola.

Y creamos una clase con el metodo main para ejecutar todo este sistema:

En este caso vamos a crear todas operaciones que los clientes quieren hacer como retirar una, dos veces o si se arrepientes de ese retiro hacer un revert del mismo.

Haciendo esto nos da la flexibilidad de apilar los comandos que necesitemos a nuestro gusto y añadir más de ellos también.

Este es el resultado que obtendríamos.

Con este patrón de diseño podríamos agregar numeras operaciones no importando el tipo de clase que las implemente, lo que nos daría muchísima flexibilidad a la hora de crear código. Ver más aquí.

Strategy

Este patrón de diseño te permite encapsular una serie de algoritmos relacionados en diferentes clases y se pueden intercambiar en tiempo de ejecución.

Un ejemplo son las distintas maneras que puedes hace un pago, con una tarjeta de crédito, débito o en efectivo, también se puede usar para añadir ciertas validaciones a un objeto en concreto.

También se puede ver en video juegos de shooting donde podemos encapsular el código que baje el nivel de sangre de un jugador cuando recibe un disparo o aún más podemos ejecutar diferentes estrategias al mismo tiempo para cambiar el estado del campo y el del nivel de sangre del jugador.

Cabe resaltar este patrón se puede minimizar usando las funciones lambdas.

Para este ejemplo vamos a agregar unas validaciones en forma de estrategia y se las aplicaremos a un método en si modificando su comportamiento.

Creamos nuestra clase ClientRequest donde vamos a recibir todas la informacion a validar.

Después creamos una interfaz en la cual creamos un método llamado execute que va a ser implementado por todas las strategias que vamos a crear.

Después creamos las estrategias que van a hacer las validaciones que vamos a aplicar a cierto objeto y se pueden crear las que se deseen y luego se registran en una cola para su posterior ejecución.

Aquí creamos nuestro objeto ClientRequest y registramos las estrategias que queremos ejecutar.

Y ya solo ejecutamos nuestro código y nos va a fallar porque no estamos enviando ningún valor en el campo del cliente.

Y así podríamos ejecutar diferentes algoritmos y cambia el estado de un objeto en tiempo de compilación sin necesidad de extender la clase.

Cabe resaltar que este patrón ya se puede simplificar con el uso de lambda expresión: Implementación strategy.

Observer

Este patrón te permite definir un mecanismo para notificar a otras clases que se han suscrito con anterioridad a un objeto sobre algún evento o cambios internos dentro del mismo.

Lo podemos encontrar en el nuevo paradigma de programación reactiva y la necesidad de crear sistemas asíncronos han llevado al fuerte uso del proyecto Reactor spring que nos permite hacer uso de la librería rxjava para todo este proceso de programación reactiva.

Cabe mencionar que estos son algunos ejemplos que he visto y lo pueden aplicar donde requiera la ocasión.

En este ejemplo vamos a simular un grupo de WhatsApp donde se van a suscribir varias personas y se les notificara a los usuarios el ingreso de un nuevo suscriptor.

Crearemos dos interfaces IObservable y IObserver con el cual creamos las abstracciones.

Luego crearemos una clase group e implementa IObservable User donde implementa la interfaz IObserver la cual dan la pertinente implementación de los métodos.

El resto de las implementaciones se encuentran en GitHub.

Para finalizar ejecutamos el método de main con la creación de los usuarios y el grupo para ver el movimiento de los nuevos suscriptores.

Y este el resultado objetivo, cuando un nuevo usuario se suscribe, los usuarios ya subscriptos al grupo son notificados.

Ver más: Implementación Observer

Les comparto el link de repositorio con los ejemplos que mostré y también la fuente de donde tomé las diferencia entre interfaz y clase abstracta.

Happy coding.

actualizado 01 Dec 2023
Erick_Romero.jpeg
autorIngeniero de Software, EPAM Anywhere

Soy un desarrollador Java con 7+ años de experiencia y altas capacidades en todos los aspectos del ecosistema Java como Spring Boot y GCP y diseñando soluciones para las personas. También soy un amante del código limpio y los algoritmos.

Soy un desarrollador Java con 7+ años de experiencia y altas capacidades en todos los aspectos del ecosistema Java como Spring Boot y GCP y diseñando soluciones para las personas. También soy un amante del código limpio y los algoritmos.