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.

package javamex.patronesdiseño.command;

import java.io.OutputStream;

public interface ICommand {

    public String getCommandName();

    public void execute(String[] args, OutputStream out);
}

 

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.

package javamex.patronesdiseño.command;

import java.util.HashMap;
import javamex.patronesdiseño.command.impl.*;

public class CommandManager {

    private static CommandManager commandManager;

    private static final HashMap<String, Class<? extends ICommand>> COMMANDS =
            new HashMap<String, Class<? extends ICommand>>();

    private CommandManager() {
        registCommand(EchoCommand.COMMAN_NAME, EchoCommand.class);
        registCommand(DirCommand.COMMAND_NAME, DirCommand.class);
        registCommand(DateTimeCommand.COMMAND_NAME, DateTimeCommand.class);
        registCommand(MemoryCommand.COMMAN_NAME, MemoryCommand.class);
        registCommand(FileCommand.COMMAND_NAME, FileCommand.class);
        registCommand(ExitCommand.COMMAND_NAME, ExitCommand.class);
        registCommand(BatchCommand.COMMAND_NAME, BatchCommand.class);
        registCommand(WaitAndSayHello.COMMAND_NAME, WaitAndSayHello.class);
    }

    public static synchronized CommandManager getIntance() {
        if (commandManager == null) {
            commandManager = new CommandManager();
        }
        return commandManager;
    }

    public ICommand getCommand(String commandName) {
        if (COMMANDS.containsKey(commandName.toUpperCase())) {
            try {
                return COMMANDS.get(commandName.toUpperCase()).newInstance();
            } catch (Exception e) {
                e.printStackTrace();
                return new ErrorCommand();
            }
        } else {
            return new NotFoundCommand();
        }
    }

    public void registCommand(String commandName, 
            Class<? extends ICommand> command) {
        COMMANDS.put(commandName.toUpperCase(), command);
    }
}

 

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.

package javamex.patronesdiseño.command.impl;

import java.io.OutputStream;
import javamex.patronesdiseño.command.ICommand;

public class ExitCommand implements ICommand {

    public static final String COMMAND_NAME = "exit";

    @Override
    public String getCommandName() {
        return COMMAND_NAME;
    }

    @Override
    public void execute(String[] args, OutputStream out) {
        System.exit(0);
    }
}

 

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.

package javamex.patronesdiseño.command.impl;

import java.io.OutputStream;
import javamex.patronesdiseño.command.ICommand;

public abstract class BaseCommand implements ICommand {

    @Override
    public abstract String getCommandName();

    @Override
    public abstract void execute(String[] args, OutputStream out);

    public void write(OutputStream out, String message) {
        try {
            out.write(message.getBytes());
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

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.

package javamex.patronesdiseño.command.impl;

import java.io.File;

import java.io.OutputStream;

import java.nio.channels.WritableByteChannel;

public class DirCommand extends BaseCommand {

    public static final String COMMAND_NAME = "dir";

    @Override
    public String getCommandName() {
        return COMMAND_NAME;
    }

    @Override
    public void execute(String[] args, OutputStream out) {
        if (args == null || args.length < 2) {
            write(out, COMMAND_NAME + " argumentos insuficientes");
        }
        String operation = args[0];
        if ("-D".equals(operation.toUpperCase())) {
            write(out, deleteDir(args[1]));
        } else if ("-N".equals(operation.toUpperCase())) {
            write(out, newDir(args[1]));
        } else {
            write(out, "Se esperaba una operación correcta -d | -n");
        }
    }

    private String deleteDir(String url) {
        try {
            File file = new File(url);
            if (!file.exists()) {
                return "El archivo no existe";
            }
            if (!file.canWrite()) {
                return "Privilegios insuficientes";
            }
            file.delete();
            return "";
        } catch (Exception e) {
            return "ERROR: " + e.getMessage();
        }
    }

    private String newDir(String url) {
        try {
            File file = new File(url);
            if (file.exists()) {
                return "El archivo ya existe";
            }
            file.mkdirs();
            return "";
        } catch (Exception e) {
            return "ERROR: " + e.getMessage();
        }
    }
}

 

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.
package javamex.patronesdiseño.command.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.OutputStream;
import java.util.Arrays;

public class FileCommand extends BaseCommand {

    public static final String COMMAND_NAME = "file";
    private static final String WRITE_APPEND = "-WA";
    private static final String WRITE_OVERRIDE = "-WO";
    private static final String WRITE_NEW = "-WN";
    private static final String RENAME_FILE = "-R";
    private static final String DELETE_FILE = "-D";

    @Override
    public void execute(String[] args, OutputStream out) {
        if (args.length < 2) {
            write(out, "Parametros insuficientes");
            return;
        }
        String operation = args[0].toUpperCase();
        String[] reduce = Arrays.copyOfRange(args, 1, args.length);
        if (WRITE_APPEND.equals(operation)) {
            write(out, writeAppend(reduce));
        } else if (WRITE_NEW.equals(operation)) {
            write(out, writeNew(reduce));
        } else if (WRITE_OVERRIDE.equals(operation)) {
            write(out, writeOverride(reduce));
        } else if (RENAME_FILE.equals(operation)) {
            write(out, renameFile(reduce));
        } else if (DELETE_FILE.equals(operation)) {
            write(out, deleteFile(reduce));
        } else {
            write(out, "No se encontro la operacion {" + WRITE_APPEND + "|" + WRITE_NEW + "|" + WRITE_OVERRIDE + "|" + RENAME_FILE + "|DELETE_FILE}");
        }
    }

    private String renameFile(String[] args) {
        String filePath = args[0];
        String newFileName = args[1];
        try {
            File file = new File(filePath);
            file.renameTo(new File(newFileName));
            return "";
        } catch (Exception e) {
            return "ERROR: " + e.getMessage();
        }
    }

    private String writeOverride(String[] args) {
        String filePath = args[0];
        String fileContent = args[1];
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                if (!file.createNewFile()) {
                    return "ERROR: Error al crear el archivo";
                }
            }
            FileWriter fileW = new FileWriter(file);
            fileW.write(fileContent.toCharArray());
            fileW.flush();
            fileW.close();
            return "";
        } catch (Exception e) {
            return "ERROR: " + e.getMessage();
        }
    }

    private String writeAppend(String[] args) {
        String filePath = args[0];
        String fileContent = args[1];
        try {
            File file = new File(filePath);
            if (!file.exists()) {
                return "ERRRO: El archivo no existe";
            }
            FileWriter fileW = new FileWriter(file, true);
            fileW.append(fileContent);
            fileW.flush();
            fileW.close();
            return "";
        } catch (Exception e) {
            return "ERROR: " + e.getMessage();
        }
    }

    private String writeNew(String[] args) {
        String filePath = args[0];
        String fileContent = args[1];
        try {
            File file = new File(filePath);
            if (file.exists()) {
                return "ERRRO: El archivo ya existe";
            }
            if (!file.createNewFile()) {
                return "ERROR: No fué posible crear el archivo";
            }
            FileWriter fileW = new FileWriter(file);
            fileW.write(fileContent.toCharArray());
            fileW.flush();
            fileW.close();
            return "";
        } catch (Exception e) {
            return "ERROR: " + e.getMessage();
        }
    }

    private String deleteFile(String[] args) {
        String filePath = args[0];
        File file = new File(filePath);
        if (!file.delete()) {
            return "No fué posible eliminar el archivo";
        }
        return "";
    }

    @Override
    public String getCommandName() {
        return COMMAND_NAME;
    }
}

 

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

package javamex.patronesdiseño.command.impl;

import java.io.OutputStream;
import java.util.Arrays;

public class EchoCommand extends BaseCommand {

    public static final String COMMAN_NAME = "echo";

    @Override
    public void execute(String[] args, OutputStream out) {
        String message = getCommandName() + " " + Arrays.toString(args);
        write(out, message);
    }

    @Override
    public String getCommandName() {
        return COMMAN_NAME;
    }
}

 

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.

package javamex.patronesdiseño.command.impl;

import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateTimeCommand extends BaseCommand {

    public static final String COMMAND_NAME = "date";

    @Override
    public String getCommandName() {
        return COMMAND_NAME;
    }

    @Override
    public void execute(String[] args, OutputStream out) {
        SimpleDateFormat dateFormater = null;
        if (args == null) {
            dateFormater = new SimpleDateFormat("dd/MM/yyyy hh:mm:ss");
        } else {
            try {
                dateFormater = new SimpleDateFormat(args[0]);
            } catch (Exception e) {
                write(out, "formato invalido");
                return;
            }
        }
        String date = dateFormater.format(new Date());
        write(out, date);
    }
}

 

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

package javamex.patronesdiseño.command.impl;

import java.io.OutputStream;

public class MemoryCommand extends BaseCommand {

    public static final String COMMAN_NAME = "memory";

    @Override
    public String getCommandName() {
        return COMMAN_NAME;
    }

    @Override
    public void execute(String[] args, OutputStream out) {
        double heap = Runtime.getRuntime().totalMemory() / 1000000d;
        double heapMax = Runtime.getRuntime().maxMemory() / 1000000d;
        double heapFree = Runtime.getRuntime().freeMemory() / 1000000d;
        String salida = "Heap: " + heap + "nMax Heap: " 
                + heapMax + "nFree Heap: " + heapFree;
        write(out, salida);
    }
}

 

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.

package javamex.patronesdiseño.command.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.util.Arrays;
import javamex.patronesdiseño.command.CommandManager;
import javamex.patronesdiseño.command.CommandUtil;
import javamex.patronesdiseño.command.ICommand;

public class BatchCommand extends BaseCommand {

    public static final String COMMAND_NAME = "batch";

    @Override
    public String getCommandName() {
        return COMMAND_NAME;
    }

    @Override
    public void execute(String[] args, OutputStream out) {
        if (args == null || args.length < 1) {
            write(out, "Número de parametros invalido");
            return;
        }
        CommandManager manager = CommandManager.getIntance();
        String[] lines = readLinesFromFile(args[0]);
        for (String line : lines) {
            String[] argsCommand = CommandUtil.tokenizerArgs(line);
            ICommand command = manager.getCommand(argsCommand[0]);
            String[] reduce = Arrays.copyOfRange(argsCommand, 1, argsCommand.length);
            command.execute(reduce, out);
            write(out, "n");
        }
        write(out, "Batch executado");
    }

    private String[] readLinesFromFile(String filePath) throws RuntimeException {
        File file = new File(filePath);
        FileInputStream stream = null;
        try {
            if (!file.exists()) {
                throw new RuntimeException("Archivo no encotrado");
            }
            stream = new FileInputStream(file);
            byte[] byteArray = new byte[stream.available()];
            stream.read(byteArray);
            String commands = new String(byteArray);
            String[] lines = commands.split("n");
            return lines;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        } finally {
            try {
                stream.close();
            } catch (Exception e2) {}
        }
    }
}

 

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.

package javamex.patronesdiseño.command.impl;

import java.io.OutputStream;

public abstract class AsyncCommand extends BaseCommand {

    public void execute(final String[] args, final OutputStream out) {
        new Thread(new Runnable() {
            public void run() {
                executeOnBackground(args, out);
            }
        }).start();
    }

    public abstract void executeOnBackground(String[] args, OutputStream out);
}

 

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! .

package javamex.patronesdiseño.command.impl;

import java.io.OutputStream;

public class WaitAndSayHello extends AsyncCommand {

    public static final String COMMAND_NAME = "waithello";

    @Override
    public void executeOnBackground(String[] args, OutputStream out) {
        if (args == null || args.length < 1) {
            write(out, "Parametros insuficientes");
            return;
        }
        Long time = null;
        try {
            time = Long.parseLong(args[0]);
        } catch (Exception e) {
            write(out, "Tiempo inválido");
            return;
        }

        try {
            Thread.sleep(time.longValue());
            write(out, "Hello!!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public String getCommandName() {
        return COMMAND_NAME;
    }
}

 

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.

package javamex.patronesdiseño.command.impl;

import java.io.OutputStream;

import java.nio.channels.WritableByteChannel;

public class ErrorCommand extends BaseCommand {

    private static final String COMMAND_NAME = "ERROR";

    @Override
    public String getCommandName() {
        return COMMAND_NAME;
    }

    @Override
    public void execute(String[] args, OutputStream out) {
        String message = "Error al invokar el comando";
        write(out, message);
    }
}

 

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

package javamex.patronesdiseño.command.impl;

import java.io.OutputStream;

public class NotFoundCommand extends BaseCommand {

    private static final String COMMAND_NAME = "NOT FOUND";

    @Override
    public String getCommandName() {
        return COMMAND_NAME;
    }

    @Override
    public void execute(String[] args, OutputStream out) {
        write(out, "Commando no 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.

package javamex.patronesdiseño.command;

import java.util.ArrayList;

import java.util.Arrays;

import java.util.List;

public class CommandUtil {

    public static String[] tokenizerArgs(String args) {
        List<String> tokens = new ArrayList<String>();
        char[] charArray = args.toCharArray();
        String contact = "";
        boolean inText = false;
        for (char c : charArray) {
            if (c == ' ' && !inText) {
                if (contact.length() != 0) {
                    tokens.add(contact);
                    contact = "";
                }
            } else if (c == '"') {
                if (inText) {
                    tokens.add(contact);
                    contact = "";
                    inText = false;
                } else {
                    inText = true;
                }
            } else {
                contact += c;
            }
        }
        if (contact.trim().length() != 0) {
            tokens.add(contact.trim());
        }
        String[] argsArray = new String[tokens.size()];
        argsArray = tokens.toArray(argsArray);
        return argsArray;

    }
}

 

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.

package javamex.patronesdiseño.command;

import java.util.Arrays;
import java.util.Scanner;

public class CommandMain {

    public static void main(String[] args) {
        System.out.println("Command Line is Start");
        CommandManager manager = CommandManager.getIntance();
        Scanner in = new Scanner(System.in);
        while (true) {
            String line = in.nextLine();
            if ("EXIT".equals(line.toUpperCase())) {
                System.exit(0);
            }
            if (line.trim().length() == 0) {
                continue;
            }
            String[] commands = CommandUtil.tokenizerArgs(line);
            String commandName = commands[0];
            String[] commandArgs = null;
            if (commands.length > 1) {
                commandArgs = Arrays.copyOfRange(commands, 1, commands.length);
            }
            ICommand command = manager.getCommand(commandName);
            command.execute(commandArgs, System.out);
            System.out.println("");
        }
    }
}

 

 

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:

date dd/MM/yyyy hh:mm:ss
dir -n "c:/dummy/dummy2"
echo "Se creo el archivo dummy2"
file -wn "c:/dummy/dummy2/dummy.txt" "Hola mundo con el comando file"
memory
file -r "c:/dummy/dummy2/dummy.txt" "c:/dummy/dummy2/dummy2.txt"
date dd/MM/yyyy hh:mm:ss

 

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.

16 thoughts to “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 *