El concepto de Command Pattern o patron comando es uno de los más conocidos en el mundo de la programación. ¿Para qué sirve el patrón comando y que situaciones resuelve?. En programación nos podemos encontrar en muchas situaciones en las que tenemos que gestionar tareas que reciben algún tipo de objeto como parámetro.
Una vez recibido este objeto deberemos procesarle. En principio es una tarea que parece muy sencilla y nos bastaría con tener una clase que implemente las diferentes tareas para el objeto .Vamos a ver un ejemplo concreto que nos ayude a clarificar. Supongamos que disponemos de una clase Producto.
package com.arquitecturajava; public class Producto { private int id; private String nombre; private double precio; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public double getPrecio() { return precio; } public void setPrecio(double precio) { this.precio = precio; } }
Deberemos realizar varias tareas :
- ValidarProducto
- EnviarPorCorreo
- Imprimir
Estamos ante una situación muy sencilla, nos es suficiente con crear una clase que disponga de tres métodos que que encarguen cada uno de una operación:
package com.arquitecturajava; public class GestorProductos { public void validarProducto(Producto producto) { if (producto.getPrecio() < 100) { System.out.println("producto valido"); } else { System.out.println("producto invalido"); } } public void imprimirProducto(Producto producto) { System.out.println(producto.getNombre()); System.out.println(producto.getId()); System.out.println(producto.getPrecio()); } public void enviarPorCorreo(Producto producto) { System.out.println(producto.getNombre() +"enviado por correo") ; } }
Sin Java Command Pattern
Los problemas surgen cuando no hay solo tres tareas que ejecutar , sino que el número de tareas crece de forma exponencial .
Esto en principio nos puede resultar raro , pero no hay nada mas que mirar nuestro programa y darnos cuentas que podemos ejecutar muchas tareas y muy diferentes sobre el concepto de producto . Una solución sencilla pasaría por construir más clases que almacenen los diferentes métodos.
Sin embargo no siempre es la mejor solución ya que genera un fuerte acoplamiento entre el cliente y los diferentes componentes. Por otro lado no siempre es sencillo decidir que tareas van en cada clase ya que con el paso del tiempo las tareas y la relación entre ellas varia en un negocio.
Java Command Pattern una solución elegante
Para solventar este problema vamos a construir un ejemplo usando Java Command Pattern . Este patrón se encarga de definir el concepto abstracto de Tarea y de construir varias clases que lo implementen
package com.arquitecturajava; public interface TareaProducto { public abstract void ejecutar(Producto producto); }
package com.arquitecturajava; public class TareaEnvioCorreo implements TareaProducto { @Override public void ejecutar(Producto producto) { System.out.println(producto.getNombre() +"enviado por correo") ; } }
package com.arquitecturajava; public class TareaImprimirProducto implements TareaProducto{ @Override public void ejecutar(Producto producto) { System.out.println(producto.getNombre()); System.out.println(producto.getId()); System.out.println(producto.getPrecio()); } }
package com.arquitecturajava; public class ValidarProducto implements TareaProducto{ @Override public void ejecutar(Producto producto) { if (producto.getPrecio()<100) { System.out.println("producto valido"); }else { System.out.println("producto invalido"); } } }
Una vez hemos construido la jerarquía de clases nos queda definir el GestorTareas que se encarga de ejecutar cada una de las tareas.
package com.arquitecturajava; public class GestorTareas { public void ejecutar (TareaProducto tarea,Producto p) { tarea.ejecutar(p); } }
Este diseño nos permite tener una mayor flexibilidad ya que cada tarea es independiente. El añadir nuevas tareas no afecta al resto de tareas. Por otro lado es muy sencillo generar nuevas clases que por ejemplo agrupen tareas.
package com.arquitecturajava; import java.util.ArrayList; import java.util.List; public class SuperTarea implements TareaProducto { private List<TareaProducto> lista= new ArrayList<TareaProducto>(); public void addTarea(TareaProducto tarea) { lista.add(tarea); } @Override public void ejecutar(Producto producto) { lista.forEach((t)->t.ejecutar(producto));; } }
Ahora podemos construir en el programa principal una super tarea:
package com.arquitecturajava; public class Principal { public static void main(String[] args) { SuperTarea st= new SuperTarea(); st.addTarea(new ValidarProducto()); st.addTarea(new TareaEnvioCorreo()); GestorTareas gt= new GestorTareas(); Producto p= new Producto(1,"tablet",100); gt.ejecutar(st, p); } }
Ejecutamos el programa y veremos el resultado en la consola:
Java Command Pattern es una de las soluciones que más flexibilidad aporta al código:
- El concepto de Java Proxy Pattern
- Adaptadores y patrones y el principio OCP
- El patrón fachada (GenBetaDev)
- Design patterns
Una explicacion notable. Gracias por compartir!
de nada 🙂
Fantástica explicación. Muchas gracias.
de nada 🙂
Hola,
la clase GestorTarea no es innecesaria?
Quedando la clase de la siguiente manera:
package com.arquitecturajava;
public class Principal {
public static void main(String[] args) {
SuperTarea st= new SuperTarea();
st.addTarea(new ValidarProducto());
st.addTarea(new TareaEnvioCorreo());
st.ejecutar(p) <=== Con esto nos ahorramos el GestorTarea
}
}
No se ver la necesidad de usarla pudiendolo hacer directamente.
Gracias y un saludo!
Hola amigo, esto es por el hecho de seguir el patrón de diseño, en donde los Cocrete Command (Las tareas individuales), se ven agrupadas por un gestionador de tareas, quien es el que se comunica directamente con el cliente. En este ejemplo se implemento una super tarea que utilizará varias de las tareas creadas, pero de igual manera, por seguir el patrón de diseño, el gestionador de tareas debe de estar ahí, ya que el puede invocar a CUALQUIER tarea.
Una fantástica explicación con un ejemplo entendible ¡Muchas gracias!
gracias 🙂
hola!!
una consulta por que me sale error en esta parte:
if (producto.getPrecio()<100)
gracias por su respuesta
En principio parece correcto , te pueden faltar las llaves?
El ejemplo más claro que encontré en internet. Muchas gracias!
de nada 🙂
Excelente explicacion, como siempre !
Por favor, mas post con patrones…
gracias 🙂
Que tal!
Pregunta: ¿Puedo en el constructor de las tareas mandar objetos que fuera a requerir antes de ejecutarlas?
no te lo recomiendo porque eso implicaría que nada mas crear el objeto ya tienes los datos necesarios de ejecución y esto no siempre es así. De hecho el patrón comando se utiliza muchas veces para generar grupos de comandos y posteriormente ejecutarlos pasando los parámetros adecuados.
Qué grande, Cecilio. Explicación elegante para un patrón elegante, desde luego.
Me surge una cuestión a partir de una idea que planteas, los grupos de comandos(evocándome los clásicos del mejor cine bélico jeje, y por supuesto los juegos de PC), seguramente porque aún no estoy familiarizado con el patrón(ejecutarlo no implica conocerlo, ciertamente).
Mi pregunta es: ¿cómo puedo entender mejor la idea “generar grupos de comandos y posteriormente ejecutarlos pasando los parámetros adecuados”?
Muchas gracias por tu trabajo.
Claro que puedes, si la clase necesita algún servicio externo para llevar a cabo su ejecución.
Eso es perfectamente posible
Yo tengo una duda, por que defines el metodo ejecutar como abstracto y no implementas nada en la interface?
Buenas porque en principio un interface dispone únicamente de métodos abstractos , hasta la llegada de java 8 y porque ademas la implementación la realizan cada una de las clases hijas.
eso es redundante e innecesario.
Uhhn no se como explicarme , no he querido decir que no se pueda realizar lo que tu comentas. Es evidente que si es posible y habrá casos en los que el comando reciba parametros cuando se construye. Pero yo personalmente prefiero crearle con los mínimos parámetros y pasárselos en el execute