En este artículo vamos a ver como darle calidad a nuestro software eliminado las múltiples sentencias if/else
o switch
que puede llegar a tener nuestras funciones. Para esto vamos a utilizar el Patrón Estrategia.
Introducción
En un artículo anterior, donde vimos el Patrón Fachadas y para que sirven, se generaron varias dudas a cerca de como quedaría la función que puse como caso de estudio. Si bien, ese artículo no pretendía mostrar el código de la funcionalidad si no, demostrar como se podía abstraer muchas lógica a una clase que funcionara como único punto de acceso (para esto sirven las Fachadas).
Pero sus comentarios me dieron el pie para explicar el Patrón Estrategia para reemplazar el gran switch()
con mucha lógica que tenia esa función. Vamos a ver como se hace ✊.
¿Qué es el Patrón Estrategia?
Los patrones de diseño son distintas formas de resolver problemas comunes que se nos presentan. El Patrón Estrategia nos permite convertir distintos comportamientos a clases separadas. Por lo tanto, el patrón de diseño Estrategia es ideal cuando tenemos un gran switch como teníamos en el artículo anterior.
Poniéndonos en tema
En el artículo de Fachadas había comentado que estaba realizando un ecommerce y una parte compleja del sistema iba a ser el desarrollo de los cupones de descuento. Cada cupón tiene su propia lógica y forma de aplicar el descuento.
Por lo tanto, para no poner un switch()
se utilizo una Fachada
para enviar toda esa lógica a una clase separada.
Pero ¿qué habría en esa clase?, ¿la fachada contendrá el switch?, ¿el switch llamará a distintas funciones?. Como dije en ese mismo artículo, la Fachada podría tener cualquier código.
Pero como todos nosotros somos programadores Pro, vamos a utilizar una solución mas fácil de mantener, mas fácil de entender y mas fácil de agrandar sin romper nada del resto del sistema 😉.
¿Cómo aplicar el Patrón Estrategia?
Como dijimos, el patrón de diseño Estrategia es ideal para reemplazar los molestos switch. Este patrón sugiere que tome una función que hace algo específico de muchas maneras diferentes y extraiga todos estos algoritmos en clases separadas llamadas Estrategias.
También, nos dice que debemos tener una clase principal que se encargue de delegar el trabajo a un objeto de estrategia, según el tipo de cupón que recibamos. A esta clase se la llama Contexto.
Pero el contexto no se encarga de seleccionar las estrategias. En cambio, la Fachada será la encargada de enviarle la estrategia al contexto.
De esta manera, el contexto se vuelve independiente de las estrategias concretas, de modo que podremos agregar nuevos algoritmos o modificar los existentes sin cambiar el código del contexto u otras estrategias.
Aplicando el Patrón Estrategia al subsistema de Cupones
Primero vamos a crear una clase por cada tipo de cupón con su lógica correspondiente.
Como podemos ver, cada una de las estrategias implementan una interfaz llamada Estrategia
. Su código sería:
Como decíamos anteriormente, cada una de estas estrategias son ejecutadas por una clase llamada Contexto. Vamos a ver como es el código de esta clase.
Analizando el código de la clase Contexto, vemos que en su constructor recibe una estrategia que luego es ejecutada en el método ejecutarEstrategia()
.
Aclaración: en el constructor vemos que su argumento es Estrategia $estrategia
. Esto no significa que estamos haciendo una inyección de dependencias. Si no, que le estamos diciendo a la clase Contexto que debe instanciarse con un argumento de tipo Estrategia.
Es por eso que podemos enviarle cualquiera de nuestras estrategias ya que cada uno de ellas implementan la interfaz Estrategia
. Esto no brinda flexibilidad y seguridad a nuestro sistema. Ya que, nos aseguramos que cada estrategias de cupones tengan la función ejecutar()
.
Ahora bien, ¿con que se come todo esto?. ¡Muy fácil! En nuestra fachada instaciamos la estrategia correspondiente y se la enviamos al contexto para que la ejecute:
El diagrama total quedaría así:
Aquí les dejo una ejecución de prueba que hice con este ejemplo.
Entiendo que en este momento estés pensando: «uff! que cantidad de archivos creados». Y si, te entiendo que pienses así, pero todo esto fue para crear la estructura del patrón Estrategia y luego sentirás sus beneficios.
Beneficios del Patrón de Diseño Estrategia
Supongamos que el día de mañana nuestro jefe nos dice que ahora solo los martes se podrá aplicar el cupón de «2×1». Y a un colega le dice que el cupón de «envíos gratis» solo será aplicable los días lunes y jueves.
El patrón Estrategia nos permitirá trabajar individualmente. Nosotros solo editaríamos el archivo DosPorUnoEstrategia.php
y nuestro compañero el archivo EnviosFreeEstrategia.php
.
Si hubiéramos tenido la lógica de cupones en un switch, tendríamos que lidiar con el merge de nuestro código y el código de nuestro colega. O peor aun! si nuestro jefe no quiere lidiar con el merge, entonces nos podría pedir que nosotros nos encarguemos de ambos desarrollos 😰.
Otro beneficio es la sencillez con la que podemos extender el subsistema de cupones agregando más funcionalidades. Supongamos que llegamos a un acuerdo con el Banco Santander que le brindara a nuestros clientes el 30% de descuento si compran con la tarjeta de crédito del Banco.
Lo único que tenemos que hacer es crear la clase DescuentoConBancoSantanderEstrategia
, implementar la interfaz Estrategia
y desarrollar la lógica necesaria. Luego, agregamos la nueva estrategia al arreglo $cuponesHabilitados
. Y listo, todo sigue muy ordenado y con código elegante.
Conclusión
Vimos una excelente forma de mejorar nuestro código gracias al patrón de Estrategias. Este patrón nos da una gran flexibilidad y organización pero no es aconsejable utilizarlo si solo tienes un par de algoritmos que nunca cambian. En nuestro caso fue ideal para nuestro subsistema de cupones.
Espero que te haya gustado este artículo. Si así fue, no dudes en compartirlo y dejar tu comentario. Y si no fue así y todavía piensas en usar un gran switch, por favor, retírate de este blog sin hacer ningún tipo de escándalo 😂. Nos vemos en la próxima.
Excelente Post! Me ayudó para implementar un switch como de 31 condiciones para autenticación y registro que a su vez se factorizó a un menor número de condiciones gracias a haber desglosado así por partes. «Divide y vencerás».
Me alegro que te haya servido el post. Saludos bro 🤙
Fabuloso, ya estoy pensando en aplicarlo en una app de transacciones donde tienen varios tipos y cada una tiene una lógica diferente.
👏 Vas a ver lo genial que es trabajar usando esto!
Muy buen contenido de altisima calidad! Solo para agregar esto aparte nos ayuda a tener implementaciones abiertas a modificaciones, osea nuevos tipos de cupon, pero cerrado a modoficaciones y tambien a mantener en algun grado responsabilidades divididas hasta cierto punto permite la implementacion de la S y la O jajaja para los que les gusta SOLID que para mi es una orientacion mas no veo positivo tener todas las letras, me facino el articulo y solo pregunta llama a tipodecupon::aplicar por el facades que crea una nueva instancia del objeto verdad?
Muchas gracias loco! Si, tal cual como dice se está utilizando la S y la O de los principios SOLID. Y si, no es necesario aplicar todos los principios en una misma implementación.
Si, estoy utilizando una fachada en tiempo real por eso utilizo directamente
TipoDeCupon::aplicar()
.Lo que hace internamente Laravel con las fachadas en tiempo real es crear una clase para manejar los métodos de tu clase (en este caso TipoDeCupon) cómo métodos estáticos.
Básicamente, nos ahorra tener que instancias la clase 😄
Esto es increíblemente elegante. ¿Donde puedo formar una lógica para crear cosas así?
Hola, no le veo el beneficio, solo basta que te digan que el cupón debe estar en la base de datos, y no estar haciendo archivos, eso ya lo implementa Magento, Prestashop, Shopify, creas los cupones y dependiendo de varios factores se aplica, y no lo hacen de la manera que pones
yo igual, pero bueno esto solo es un ejemplo para ver la forma de aplicación del patrón estrategia
Estás hablando de algo muuuy diferente. Cuando te toque implementar una lógica compleja de programación y no un CMS capaz la veas. Saludos.
Muy buen articulo, este tipo de patrón estrategia, seria utilizable también para hacer una selección de tipo de pago en el caso de aceptar pagos por ejemplo con paypal via api, stripe, diferentes tipos de criptomonedas… Cierto?
Así es! Todo lo que sea aplicar distintas lógicas (o estrategias) este patrón te ayuda mucho.
Muy buen articulo, muy explicativo. Felicitaciones
Muchas gracias 😀
Tal vez ya lo conozcan pero reco el libro Patrones de Diseño de GOF Gamma.
Si, es muy bueno y de lectura obligada!
Como haces para utilizar el TiposDeCupon::aplicar referenciandolo staticamente cuando la función aplicar no es static??
Fachadas, verdad?
Si, con fachadas en tiempo real. En este artículo las explico:
https://www.laraveltip.com/para-que-sirven-las-facades-en-laravel-explicadas-con-un-caso-real/
Saludos.
Ahora me queda mucho mas claro el patrón. Muchas gracias!
Exelente post, gran trabajo!!!, saludos desde cuba 🙂
Muchas gracias José. Saludos!
Excelente! Me dio una idea bastante más clara de como abordar una aplicación donde cada objeto debe tener una lógica diferente, pero pertenecer a la misma clase. Saludos!
Hola Matias, dos preguntas. Por qué la clase contexto ? Desde la misma clase TiposDeCupon no era suficiente para llamar el tipo de cupón que se necesita ? la segunda ? Porque pasas una variable cupon de tipo Cupon como parámetro en el método aplicar de TiposdeCupon ? Cuál sería la intención de eso ? Gracias
Cómo estas Joel?
Tu primer pregunta surge bastante seguido así que, muchas gracias por hacerla. Como vos decís, en este si se podría haber obviado la clase
Contexto
y, si lo hubiera obviado, el contexto sería el métodoaplicar()
. ¿Qué quiere decir esto? Que los patrones no deben aplicarse a raja tabla como están expuestos en el libro de Erich Gamma. Si no serían muy poco flexibles. En mi ejemplo puse la versión «full» como la que figura en el libro para que se entienda la idea. Lo importante es eso, entender la idea de cada patrón.Con respecto a la segunda pregunta, paso la variable
$cupon
porque el descuento se va a aplicar a dicho cupón. Mira la claseDosPorUnoEstrategia
donde trato la variable$cupon
.Cualquier duda, vuelve a preguntar.
Saludos.
gracias bien explicado lo implemente en c# funciona bien
Me alegro, gracias!
Buen dia Matias, esto tambien lo puedes realizar con el patron factoria, entonces cual es la diferencias? los multiples if? ya que por el tipo de cupon necesitas una clase diferente y tambien usando el patron factory igualmente esta desacoplado ya que si se tiene que modificar diferentes tipos de cupones se puede modificar de forma independiente, entonces tambien se podria hacerlo de esa forma o no estaria bien aplicar este patron?
Claro, es valido lo que decís porque varios patrones se parecen entre sí. Es más, si mal no recuerdo, el diagrama del patrón States es exactamente igual al de Strategy.
Entonces, ¿qué patrón usar? La decisión te la definición del patrón.
Primero que el Factory es un patrón de los llamados de «Construcción» y los otros dos son de «Comportamiento».
Entonces el Factory acá no aplica porque no tenemos que construir objetos para solucionar el problema.
¿Y el State? Tampoco, porque el State aplica una lógica cuando el estado de un objeto cambia.
¿Y por qué el Strategy? Porque el patrón dice, cuando tengas varios comportamientos distintos, separalos en distintas clases.
Su definición aplica justo para el problema que se intenta resolver.
Espero haber respondido tu consulta. Y si no, volve a preguntar 🙂
Buenisimo, amigo. Muchas veces habia lidiado con códigos que usaban ese patron, y me era muy dificil comprenderlo. Y mas dificil aun tratar de adactarme, ya lo has dejado todo muy claro y con un ejemplo practico secillo y bien explicado. Me interesa muchisimo seguir practicas de programación orientada a exponer la secilles y escalado de la codificación. Nos abre puertas, no dude en recomendarme otras lecturas. Mis saludos y gracias por este articulo.
Hola como estas? Muchas gracias por tu comentario, te agradezco!
Te puedo recomendar varias lecturas, justo acabo de ver en Twitter una persona que publicó una buena lista de libros: https://twitter.com/evrtrabajo/status/1445764153239425034?t=1oME_skF5tjCnto_5vMTjw&s=19
Agregaría algunos más pero con eso ya tenes una excelente base para profundizar.
Después me comentas como te fue?
Saludos!