¿Cuándo usamos Command?
Si estás acá quiero pensar que es porque ya leíste o conocés el patrón Command. Si no es así, podes leerlo acá: en inglés o en castellano.
Es normal leer el patrón, ya sea de algún libro o de un artículo online, y quedarse con una idea de que uno lo entiende y sabe cómo aplicarlo. Pero a la hora de hacerlo se presentan dudas. También puede pasar que el contexto en donde aplicarlo esté en frente de nosotros y no nos demos cuenta.
La idea de este artículo es entender cuándo estamos en presencia de un problema en dónde este patrón nos va a ser de ayuda.
El principal valor de este patrón es “encapsular la petición” o “desacoplar la invocación de un método”, pero ¿por qué es esto valioso o útil?
También habla de otros roles: Invoker, Receiver, Client. ¿Por qué son necesarios? ¿Siempre se necesitan? ¿Quiénes serían concretamente en mi situación?
Vamos a presentar diferentes escenarios en donde el patrón Command es útil y veremos qué nos aporta, por qué lo necesitamos y daremos una idea de cómo implementarlo en cada caso.
Operaciones
Un caso de uso del patrón sería en un sistema donde se realiza una acción desde diferentes lugares, en diferentes contextos.
Por ejemplo, en un sistema que maneja envíos desde un depósito puede ser que estos se disparen por una compra, un retorno o por un traslado de productos a otro depósito.
En todos los casos es la misma operación (tal vez con diferentes parámetros) y, por lo tanto, deberíamos usar la misma pieza de código. Ahí queremos usar el patrón Command para encapsular esta lógica.
Botones
Tengo una interfaz gráfica. Un editor de texto, por ejemplo.
Tengo el botón para poner en negrita un texto. Y tengo el shortcut que pone en negrita la selección.
Cuando se crea el botón en la interfaz además se crea una instancia de Command asociado al botón para que cuando lo presionen aplique negrita (ya sea al texto seleccionado, o que active la edición en negrita de ahí en adelante).
Además, cuando la interfaz atrapa el evento Ctrl-B, crea una instancia del mismo Command y lo ejecuta.
De esta forma, el patrón nos permite de nuevo encapsular una misma lógica y usarla desde diferentes lugares.
Cada lugar va a parametrizar ese Command de forma diferente, obteniendo los datos de diferente manera.
Logs
Supongamos que en mi sistema quiero registrar algunas o todas las operaciones que se llevan a cabo.
Lo primero que debería hacer es aplicar el patrón Command para tener todas esas operaciones modeladas como comandos.
Una vez hecho eso puedo, en un solo lugar, poner un método que registre el nombre de la operación, la hora y listo! Con una sola pieza de código puedo registrar todas las operaciones del sistema.
Si necesito más detalle, puedo hacer que cada comando defina su propio texto de log.
Si construir ese texto de log es más complejo puedo usar un objeto externo que tome como parámetro al comando, le extraiga la información necesaria y la registre.
Pero el principal valor es que tengo un solo lugar desde el cual se registran las diferentes operaciones que se hacen en el sistema. Y toda la complejidad que pueda tener eso tiene un solo origen y lo puedo rastrear fácilmente.
Undo (Deshacer)
Suponiendo que tengo ya mis comandos hechos, puedo agregar la funcionalidad de deshacer una operación.
Acá es donde se empiezan a usar más partes del patrón.
Primero tengo que hacer que cada vez que se invoca un Command sea a través de un Invoker (ver el patrón Command). De esta forma, el cliente crea el Command, obtiene el Invoker, y le pasa el Command ya creado al Invoker para que lo ejecute.
El Invoker le preguntará al Command si soporta Undo.
Si lo soporta, entonces le pide un Memento (ver patrón) y lo agregará a una pila de operaciones hechas.
Si no lo soporta puede levantar una advertencia de que esta operación no se puede deshacer y, si el usuario elige proseguir, vacía la pila de operaciones hechas.
Interacciones
Supongamos que tengo un endpoint de un API REST que maneja un request HTTP.
Ese request se mapea a un mensaje a un objeto de mi sistema.
Pero en el medio yo voy a querer hacer validaciones. Muchas.
Entonces puedo crear un Command.
El manejador del request extraería los parámetros del request, se los pondría como parámetros al Command y luego el Command se ejecutaría.
El Command podrá validar los parámetros, devolver los errores que se detecten o ejecutar normalmente y devolver el resultado apropiado si los parámetros no presentan errores.
Si tengo otro endpoint REST ―o una línea de comando o un endpoint Web― que quiere realizar la misma operación puedo reutilizar el Command.
Simulaciones
Durante las simulaciones se realizan diferentes actividades. Si quiero registrar o llevar una estadística o métrica de algunas de ellas puedo crear un objeto que modele la ejecución de dicha actividad (Command) y, a partir de ahí, puedo registrar cuándo y cómo se realiza.
De paso, tengo la lógica de esa actividad modelada en un objeto.
Macros
Supongamos que los usuarios suelen realizar ciertas combinaciones o conjunto de operaciones repetidas veces.
Por ejemplo, en un editor visual se suele repetidamente hacer lo siguiente: cortar, aplicar filtro “Blur”, redimensionar a 50x50 píxeles y rotar 90 grados.
Entonces, queremos agregar la funcionalidad de poder grabar esas operaciones, asignarles un shortcut y luego realizarlas con una sola invocación del mismo.
Como se imaginarán a esta altura, para esto tendríamos que ya tener modeladas nuestras diferentes acciones en Commands. Pero, además, tendría que agregar nuevos Commands de “Empezar a Grabar Macro”, “Terminar de Grabar Macro” y uno de “Ejecutar Macro Grabada”.
Luego tendría que hacer que mis Commands siempre sean ejecutados por un Invoker, quien se encargará de ver si se está grabando una Macro e ir guardando la operación ejecutada.
Scripting
En aplicaciones complejas puedo querer que mis usuarios puedan realizar las distintas operaciones de mi aplicación sin usar la interfaz gráfica, sino invocándolas desde un lenguaje de scripting.
Para eso necesitaría tener modeladas las operaciones de mi aplicación en Commands y montar un engine sintáctico que procese esos textos de lenguaje de scripting y los convierta en los Commands que se van a ejecutar.
Conclusiones
Como vimos en los ejemplos anteriores, la aplicación del patrón Command resuelve una gran variedad de problemas. La mayoría de ellos tienen que ver con la aplicación y cómo se interactúa con nuestro sistema. El principal valor que nos da es el de proveer un único lugar para la lógica de las acciones de nuestro sistema y, además, nos da la capacidad de manipular esas acciones de diferentes formas.
Espero que con esto les quede un poco mas claro cómo usar el patrón. Si tienen dudas o nuevos ejemplos, no duden en escribirme para ver cómo manejar esos casos.
El patrón Command dispara un sinfín de problemáticas a resolver y estaremos escribiendo más artículos relacionados al mismo y las problemáticas que resuelve y lo rodean.
¡Hasta la próxima!