Patrón de diseño Command

Patrón de diseño CommandEl patrón de diseño Command es muy utilizado cuando se requiere hacer ejecuciones de operaciones sin conocer realmente lo que hacen, estas operaciones son conocidas como comandos y son implementadas como una clase independiente que realiza una acción muy concreta, para lo cual,únicamente recibe un conjunto de parámetros para realizar su tarea.

La siguiente imagen muestra la estructura del patrón de diseño:

Patrón de diseño Command
Fig.1: En la imagen podemos apreciar cómo está diseñado el patrón de diseño Command.

 

En la imagen podemos apreciar los siguientes elementos:

ICommand: Esta es la pieza más importante del patrón, ya que cada comando deberá implementar esta interface y deberá implementar el método execute, el cual, de forma polimórfica realizara la operación que deseamos. Este componente generalmente es una interface o una clase abstract.

Concrete Command: Para este componente hablaremos en plural, ya que cada Concrete Command que tengamos implementado se convertirá en un comando en nuestro patrón. Este componente tiene que implementar ICommand de forma directa o indirecta y deberá implementar el método execute, en el cual se realizara la operación que deseamos que realice el comando.

Command Manager: Este componente nos servirá para administrar los comandos que tenemos disponibles en tiempo de ejecución, desde aquí podemos ejecutar un Comando o registrar nuevos comandos para que estén disponibles en tiempo de ejecución. Esta clase es importante, ya que de nada sirve tener un conjunto de Concrete Command que implementan ICommand regados por nuestro proyecto si no tenemos un componente que los administre y nos permite ejecutarlos.

Invoker: El invoker representa a la acción que dispara alguno de los comandos, un invoker podría ser un usuario, una aplicación o algún evento generado por nuestra aplicación.

Ya que conocemos los componentes es hora de hablar un poco más del Patrón de diseño, Al comienzo comentamos que nos sirve para realizar operaciones sin conocer realmente que hace la operación. Para lo cual utilizaremos un ejemplo y veremos de qué forma este patrón lo resuelve.

A estas alturas me imagino que todos ya hemos utilizado alguna vez la consola de Windows o Unix. Si observamos la consola, veremos que ella response a base de comandos, estos comandos pueden o no necesitar parámetros para funcionar, lo asombroso aquí es que la consola tiene la característica de saber siempre que operación va a realizar sin tener que decirle que clase o que software utilice para realizar la tarea, ya que la consola es capaz de determinar el comando utilizado y ejecutar el componente o software que realizar la operación deseada.

Otro punto importante es que la consola tiene la característica de agregarle comandos a medida que instalamos nuevo software como es el caso de Java, si intentamos ejecutar el comando java –version, con una computadora la cual no tiene instalado Java veras que te arrojara un mensaje de comando no encontrado, sin embargo si tu ejecutas de nuevo el comando una vez instalado java, entonces obtener un mensaje como el siguiente java version “1.7.0_51″….., En el patrón Command podemos registrar nuevos comandos mediante el componente CommandManager lo cual lo veremos más a delante.

Para analizar mejor este Patrón de diseño analizaremos el ejemplo de la consola y desarrollaremos una mediante el Patrón Command. Para iniciar el ejemplo tendremos que pensar primero en la funcionalidad que le daremos a nuestra consola, en este ejemplo desarrollaremos comandos muy simples pero útiles que ayuden a demostrar la utilidad del patrón, para lo cual les dejo la siguiente imagen:

Patrón de diseño Command
Fig.2: La siguiente imagen muestra los comandos que desarrollaremos en el ejemplo.

Podemos identificar a los comandos de forma muy simple ya que son todos aquellos que implementan ICommand de forma directa o indirecta, cada comandos tiene un nombre diferente el cual lo utilizaremos para ejecutarlos desde nuestra consola. La explicación de los comandos está a continuación.

Exit: finaliza la consola y termina con nuestro programa.

Memory: Nos muestra en pantalla la cantidad de memoria total disponible, utilizada y máxima que podemos utilizar.

Batch: Comando que ejecuta en lote un conjunto de comandos definidos en un archivo plano.

Echo: Comando que nos imprime en pantalla lo que escribimos en la consola.

Date: Comando que nos imprime en pantalla la hora y la fecha en el formato que queramos.

File: Comando que nos permite interactuar con archivos, como crear, borrar, escribir, etc.

Dir: Nos permite realizar operaciones con directorios, ya sea crearlos o borrarlos.

WaitHello: Comando que nos permite demostrar la ejecución en segundo plano, este comando al ejecutarse escribe “Hello!” Después de un tiempo determinado.

A demás de los comandos tenemos los Abstract Command, los cuales están representados en el diagrama de color azul, estos comandos no hacen una funcionalidad como tal, sin embargo ayudan a crear otros comandos más complejos, los Abstract Command que tenemos aquí hace lo siguiente:

BaseCommand: Comando que agrega únicamente el método write el cual nos ayuda a escribir en pantalla los eventos generados por nuestros comandos, además de que nos permite tener un método estándar de escritura que podría ser modificado para afectar a todos los comandos, ahorrándonos el problema de modificar cada comando para que escriba de forma distinta.

AsyncCommand: Este comando tiene como finalidad demostrar que también se pueden crear comandos que se ejecuten en segundo plano, este comando tiene la característica que ejecuta la acción en un hilo independiente por lo que podemos seguir trabajando con la consola sin que el comando haya terminado de ejecutarse. Otro punto importante es que implementa el método execute, pero agrega el método abstracto executeOnBackground, en el cual deberemos implementar la función que queremos que realice en segundo plano.

Patrón de diseño Command
Fig.3: La imagen muestra un diagrama de secuencia de cómo se lleva a cabo la ejecución de un comando.

 

Implementación:

Una vez platicado el escenario podemos pasar a la implementación de la consola.

Clase ICommand: Interface que tiene como propósito definir la estructura mínima de un comando, Esta interface define dos métodos, getCommandName el cual regresa el nombre del comando para uso informativo, También tiene el método execute el cual es implementado por los comandos para realizar la operación deseada, el método recibe un arreglo de String que corresponde a los parámetros del comando y un OutputStream el cual utilizaremos para ir notificando al usuario todas las cosas relevantes como un mensaje o un error.

 

Clase CommanManager: Esta clase es utilizada para gestionar los comandos disponibles en nuestra aplicación, desde esta clase podremos registrar nuevos comandos y recuperar los comandos solicitados por el usuario, La clase contiene los siguientes métodos:

  • getCommand: Este método recibe como parámetro un String el cual corresponde al nombre del comando solicitado, El comando es buscado y retornado, pero si un comando no es encontrado se regresa un comando de error el cual explicaremos más a delante.
  • registCommand: Método utilizado para registrar los commandos que tendremos disponibles en tiempo de ejecución, para que un comando esté disponible en la aplicación primero tendrá que ser registrado.
  • getIntance: Método que nos permite obtener una instancia de la clase CommanManager ya que la esta clase sigue el patrón de diseño Singleton(Hablare de el en otra entrada).

Un punto importante a resaltar el constructor de la clase, en el cual registramos todos los comandos disponibles de forma predefinida para que estén disponibles en tiempo de ejecución, por defecto, cualquier comandos que creemos y que no se encuentre registrados no podrá ser reconocido.

 

Commandos: Las siguientes clases representan las implementaciones de los comandos que tendremos disponibles en la aplicación, los cuales fueron registrados en el constructor de CommandManager. Las clases siguientes representan los comandos que analizamos en la figura 2.

Clase ExitCommand: Comandos que termina con la aplicación. Este comando implementa ICommand por lo que representa unos de los comandos más simples de la aplicación.

 

Clase BaseCommand: Esta clase implementa ICommand, sin embargo no es como tal un comando ya que es una Clase Abstracta, Esta clase fue diseñado como ejemplo para servir como una base para los demás comandos que imprimen un mensaje en pantalla, esta clase únicamente agrega el método write, El cual es utilizados para que todos los comandos impriman en pantalla los mensajes de una forma controlada, ya que si decidimos cambiar la forma que se escribe solo tendremos que cambiar este método y todas las clases que la heredan serán afectadas.

 

Clase DirCommand: Comando que nos permite crear o borrar una carpeta de nuestro FileSytem, el comando recibe como parámetro la operación a realizar (crear o borrar {-N|-D}) más la ruta de la carpeta.

 

FileCommand: Comando que nos permitirá realizar operaciones con archivos, las operaciones son las siguientes:

  • WRITE_APPEND = -WA: Permite escribir en un archivo conservando el contenido existente previamente. Recibe dos parámetros, el primero corresponde al path del archivo y el segundo el contenido a escribir
  • WRITE_OVERRIDE = -WO: Permite escribir en un archive sobrescribiendo su contenido. Recibe dos parámetros, el primero corresponde al path del archivo y el segundo el contenido a escribir.
  • WRITE_NEW = -WN: Permite crear un archive y luego escribir en él. Recibe dos parámetros, el primero corresponde al path del archivo y el segundo el contenido a escribir.
  • RENAME_FILE = -R: Permite renombrar un archive existente. Recibe dos parámetros, el primero corresponde al path del archivo y el segundo corresponde al nuevo nombre.
  • DELETE_FILE = -D: Permite borrar un archive. Recibe únicamente el path del archivo a eliminar.

 

EchoCommand: Comando que únicamente imprime en pantalla lo que se escribe. Como un Eco.

 

DateCommand: Comando que nos permite conocer la fecha y hora del sistema en el formato que deseemos. El comando únicamente recibe como entrada el formato en el que queremos ver la fecha y hora.

 

MemoryCommand: Comando sin parámetros que nos arroja la información de la memoria que utiliza nuestra aplicación.

 

BatchCommand: Commando complejo que nos permite abrir un archivo de comandos, este archivo es parecido a los archivos en batch ya que podemos escribir un conjunto de comandos que serán ejecutados en el orden en el que se encuentran en el archivo(Se asume que cada línea es un comando). El comando únicamente recibe como parámetro el Path del archivo a ejecutar.

 

AsyncCommand: Esta clase al igual que BaseCommand no es como tal un comando ya que es una Clase Abstract, esta clase tiene como propósito demostrar que podemos implementar comandos que se ejecuten en segundo plano o de forma asíncrona.

Esta clase agrega el método executeOnBackground en el cual deberemos implementar la funcionalidad que deseamos que realice el comando. Ya que el método original execute es sobre escritor para ejecutar el método executeOnBackground en un hilo independiente.

 

Clase WaitAndSeyHello: Comando que extiende de AsyncCommand, lo cual lo convierte automáticamente en un comando que se ejecuta en segundo plano. Ha este comando únicamente se le envía un tiempo en milisegundos, el cual lo utilizara para esperar este tiempo antes de decir Hello! .

 

Clase ErrorCommand: Este comando no esté definido en nuestro diagrama y es utilizado por el CommandManager únicamente para regresar un Error al intentar instanciar un comando. Este comando no puede ser ejecutado por el cliente ya que no es un comando registrado, sin embargo CommandManager lo maneja como un comando propio del sistema para representa un error.

 

Clase NotFoundCommand: Comando parecido a ErrorCommand pero este es regresado cuando un comando no es encontrado.

 

Otra clase que se utiliza es CommandUtil, la cual la utilizamos únicamente para procesar los datos tecleados por el usuario y dividir las instrucciones en tokens, los tokes pueden ser el nombre del comando y los parámetros. La regla que se sigue para dividir los comandos es la siguiente.

Cada palabra separada por un espacio es considera como un token a excepción que se encuentre en medio de comillas dobles, ejemplo “Hola Mundo”, en este caso toda la oración es considera un único token.

 

Hasta este momento ya tenemos definidos el CommandaManager y todos los comandos que utilizaremos, sin embargo no tenemos un mecanismo que nos ayude interactuar con los comandos, para eso tenemos la clase CommandMain, la cual servirá de invoker, Esta clase se cicla para solicitar al usuario comandos desde la consola.

 

 

Introducción a los patrones de diseño
¿Quieres aprender más patrones de diseño como este? te invito a que veas mi libro.

 

 

 

Ejecución:

Finalmente, ya tenemos listo todo, por lo que comenzaremos ejecutando la aplicación y tendremos como resultado lo siguiente:

Patrón de diseño Command
Resultado de la ejecución

Iniciaremos ejecutando el comando date y le pediremos que nos arroje la fecha en el formato “DD/MM/YYYY”, seguido le pediremos la hora en el formato “HH:MM:SS” y finalmente le pediremos fecha y hora el formato “DDMMYYYY HHMMSS” para lo cual utilizaremos la nomenclatura estándar de java para la fecha y hora la cual podremos apreciar en la siguiente pagina https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html.

Patrón de diseño Command
Resultado de la ejecución

Para el siguiente ejemplo, utilizaremos el comando Dir para crear un nuevo directorio y luego lo borraremos.

Patrón de diseño Command
Resultado de la ejecución

 

Patrón de diseño Command
Resultado de la ejecución

Para el siguiente ejemplo utilizaremos el comando echo para decir “Hola Mundo”.

Patrón de diseño Command
Resultado de la ejecución

Para el siguiente ejemplo utilizaremos el comando File para crear un nuevo archivo de texto y escribir en el “Hola Mundo”

Patrón de diseño Command
Resultado de la ejecución

El siguiente comando a utilizar es Memory, El cual nos dará datos acerca de la memoria de la aplicación.

Patrón de diseño Command
Resultado de la ejecución

Utilizaremos nuevamente el comando File para renombrar el archivo dummy.txt a helloword.txt

Patrón de diseño Command
Resultado de la ejecución

Hasta aquí, solamente hemos ejecutado comandos síncronos, pero en este ejemplo ejecutaremos el comando WaitAndSeyHello, el cual lo ejecutaremos y le diremos que espere 10 segundos antes de decir Hello!.

Patrón de diseño Command
Resultado de la ejecución

Después de esperar 10 segundos tenemos el siguiente resultado.

Patrón de diseño Command
Resultado de la ejecución

En el siguiente ejemplo ejecutaremos el comando Batch el cual lee un archivo de texto y ejecuta todos los comandos que tenemos en el, para este ejemplo ejecutaremos el comando y le diremos que lea un archivo en la ruta c:/dummy/batch.txt el cual tendrá el siguiente contenido:

 

Lo que esperemos que realice el comando es lo siguiente: imprime la fecha y hora, cree el directorio c:/dummy/dummy2, escriba en pantalla Se creó el archivo dummy2″, cree un archivo en “c:/dummy/dummy2/dummy.txt” con el contenido “Hola mundo con el comando file”, imprima en pantalla los datos de la memoria, renombre el archivo que acabamos de crear y le ponga de nombre dummy2.txt y finalmente imprima nuevamente la fecha y hora.

Patrón de diseño Command
Resultado de la ejecución

 

Otra cosa que podemos intentar es ejecutar un comando que no existe, por ejemplo “java”, el cual nos indicara que no fue encontrado.

Patrón de diseño Command
Resultado de la ejecución

Finalmente ejecutamos el comando Exit, el cual terminara con la aplicación.

Patrón de diseño Command
Resultado de la ejecución

Hasta este punto ya habremos creado una consola que es capaz de ejecutar comandos, los cuales serán ejecutados sin saber realmente que clase está realizando el trabajo, sin embargo en la vida real no nos interés saber cómo es atendido un comando siempre y cuando realice la tarea que esperamos que realice. Y como ya vimos con el patrón de diseño Command esto es posible.

Ya con esta base tú podrás crear nuevos comandos y registrarlos al CommandManager para que los puedes utilizar en tu consola, también podrías crear un comando que se encargue de registrar nuevos comandos desde algún Jar para que esté disponible desde la consola. O podrías modificar la forma en la carga los comandos al inicio para que lea algún archivo de configuración.

Acabamos de ver uno de los muchos aplicativos que tiene el Patrón de diseño Command, Si tú tienes alguna experiencia con este patrón por favor coméntala.

 

Ya está a la venta mi nuevo libro “Introducción a los patrones de diseño“, en donde expongo los 25 patrones de diseño más utilizados y explicados desde un enfoque del mundo real, con ejemplos que pueden ser aplicados en tus proyectos. Sin duda es unos de los libros más completos de patrones de diseño en español.

Artículos relacionados

Patrón de diseño – Proxy En esta entrada les hablare del patrón de diseño Proxy, el cual es uno de mis favoritos ya que nos permite hacer una gran cantidad de cosas sin que el...
Introducción a los patrones de diseño (video) https://youtu.be/rrcCv8wwnlE En esta charla explicaré la importancia de los patrones de diseño, así como las ventajas que te traerá en tu crecimien...
Patrón de diseño – Composite El patrón de diseño Composite nos sirve para construir estructuras complejas partiendo de otras estructuras mucho más simples, dicho de otra manera, p...

Oscar Blancarte

Ideológico, Innovador y emprendedor, Padre, Tecnólogo y Autor, amante de la ciencia y la tecnología en todos sus colores y sabores. Arquitecto de software & Full Stack Developer con experiencia en la industria del desarrollo de software y la consultoría. Amante de la programación y el Ajedrez.

16 comentarios en “Patrón de diseño Command

  1. Buen post, gracias desde Perú por eso. Quisiera que me puedas explicar sobre estas importaciones:
    import javamex.patronesdiseño.command.CommandManager;
    import javamex.patronesdiseño.command.CommandUtil;
    import javamex.patronesdiseño.command.ICommand;

    la versión de java que utilizo es la 8.0 y allí solo me permite import java o import javax, y esto me dice que no existe. Gracias por su ayuda

    1. Hola Melina.
      Las clases CommandManager, CommandUtil y ICommand son clases que están definidas en el mismo Post, dale una revisada de nuevo y las veras.
      *CommandManager se encarga de Administrar y registrar los comandos.
      *CommandUtil la utilizo como utilidad mas que nada para desglosar los comandos introducidos.
      *ICommand es la interface común para todos los comandos

      saludos.

      1. Muchas gracias por su pronta respuesta, si ya entendí el porque de esas importaciones.

        Me han pedido que desarrolle un proyecto de interprete de comandos y que sea un ejecutable (libro SISTEMAS OPERATIVOS de W. Stalling pág. 154), y me piden que interaccione con el Disco C o el Disco para ver todo su directorio y no he logrado realizarlo, en el filecommand usted logra poner o quitar carpetas en el disco C, quisiera pedirle su ayuda para saber cómo poder ver todo lo que te tiene el disco C y el disco D a través de la consola de java. Muchas gracias de antemano.

        1. Si lo que buscas es listar el contenido del directorio C: este código te puede servir.

          public static void main(String[] args) {
          File cFile = new File(“C:\”);
          System.out.println(“C: ” + cFile.getAbsolutePath());
          File[] file = cFile.listFiles();
          for(File f : file){
          System.out.println(“File > ” + f.getAbsolutePath());
          }
          }

          Este código te imprimirá en pantalla todo lo que este en la Raíz de C.
          Espero que eso sea lo que estabas buscando.

          saludos.

  2. Buenos días, cuál sería el código en java para limpiar la consola, quiero emular al cls de cmd o al clear de linux, pero no he encontrado la combinación exacta para adjuntar a mi proyecto.

    Agradeceré me ayude con el código correcto.

    1. Hola, no podrás como tal limpiar la consola, pues en realidad cuando ejecutas el programa desde el IDE por ejemplo no estas utilizando la consola de Windows o Linux, en realidad lo que hace el IDE es tomar todo lo que salga del System.out e imprimirlo en el TextArea que vez, para poder “Limpiar la consola” tendrías que implementar tu propia pantalla en la que tu tengas control sobre el TextArea sobre el que se esta escribiendo, en la implementación que yo hago a qui no tiene esa característica.

      1. Gracias por su pronta respuesta. Lo que se ha realizado es ingresar ciertas cantidad de líneas en blanco y empezar de nuevo la cabecera de nuestro programa. Pero lo que aún nos falta es como mostrar la variable de entorno, nos han recomendado que use System.getenv(); o también set javahome, usted que tiene un amplio conocimiento quisiera nos de ideas mas claras de qué son las variables de entorno y cómo hacer el código en java. Gracias por todo.

        1. Hola Melina.
          Es verdad, el método getenv te puede servir, podrías utilizar el siguiente código para imprimirlas en pantalla.
          public static void main(String[] args) {
          Map env = System.getenv();
          Set

          > entrySet = env.entrySet();
          for (Map.Entry envVar : entrySet) {
          System.out.println(envVar.getKey() +” > ” + envVar.getValue());
          }
          }

          Las variables de entorno son y disculpa la redundancia Variables que se configurar desde el sistema operativo y al momento de iniciar la maquina virtual de java, por lo general en estas variables se establecen rutas a directorios donde por ejemplo esta el Java, librerias o cualquier cosa que programa requiera en tiempo de ejecución.

          Por ejemplo el JAVA_HOME es una variable que almacena la ruta donde esta instalado Java que por lo general es c://programa files/java/javaXX.XX.

          Espero que la respuesta sea lo que esperabas.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *