Patrón Comando

El patrón comando lo podemos utilizar cuando deseamos encapsular el llamado a ciertos métodos, es decir, cuando no deseamos exponer a los clientes la forma en que se realizan los procesos.

Empecemos con un ejemplo que nos ayude a comprender un poco mejor la idea de este patrón, el ejemplo es tomado del libro Head First Design Patterns.

Cuando llegamos a un restaurante, nos atiende un mesero que se va a encargar de tomarnos la orden con lo que deseamos comer. Esta persona escribe todos los detalles de nuestra orden.

Luego, ingresa el pedido con todos los detalles en el sistema de comandas. Este sistema imprime la comanda que va a ser usada por el chef y sus asistentes de cocina. Aquí, se encargan de cocinar los alimentos y armar el plato con lo que ordenamos. Una vez que esta listo el plato, el mesero nos lo entrega en la mesa y podemos comenzar a comer.

El patrón comando, vendría a ser todo el proceso, desde el pedido hasta la elaboración de los alimentos. ¿Los que ordenan los alimentos participan del proceso de cocción? ¿Saben cómo cocinarlos y prepararlos según el menú? Por supuesto que no. Eso no nos interesa, solo nos interesa tener nuestro plato servido y que sea lo que habíamos ordenado.

Los pasos, técnicas, herramientas o personas necesarias para lograrlo no nos incumbe. No nos interesa cómo lo hacen, solo nos importa que lo hagan.

Este patrón se compone de las siguientes 4 partes:

Comando
Es una interfaz que contiene los métodos o acciones que se pueden ejecutar.

Recibidor/Receptor
Estos son los objetos que conocen cómo se ejecutan los comandos. Por lo general, son enviados por parámetro a los comandos.

Invocador
Es el que se encarga de invocar los comandos. Estos objetos no saben que comando en realidad se está invocando, ya que solo conocen los métodos de la interfaz del Comando, no su implementación.

Cliente
Es el que se encarga de dictar cuales comandos van a ser ejecutados por medio de los invocadores.

Este sería el diagrama de clases sugerido, según Head First Design Patterns.

Diagrama de clases sugerido para el patrón comando

El Cliente es el responsable de crear los Comandos Concretos y asignar el Recibidor de cada uno.

El invocador contiene un Comando que, en algún momento, es ejecutado al utilizar alguno de sus métodos.

Comando es la interfaz para todos los comandos que utiliza el Recibidor para realizar la acción.

Los Comandos Concretos funcionan como puente entre los Recibidores y las acciones. El Invocador en realidad utiliza un Comando Concreto para ejecutar las acciones.

El Recibidor es el encargado de ejecutar la acción necesaria cuando es requerido.

Head First Design Patterns nos define el patrón comando de la siguiente manera:
El patrón comando, encapsula una solicitud como un objeto, de esta manera, permite parametrizar diferentes objetos con diferentes solicitudes.

Diagrama que ejemplifica el encapsulamiento

Para el código de ejemplo, pensemos que nos dan el requerimiento de ejecutar una consulta a diferentes motores de bases de datos. Por ejemplo, obtener el empleado con el ID = 1. Los motores de base de datos son Oracle y MSSQL Server.

Existen varias formas de implementar una solución para este requerimiento, sin embargo, practiquemos el patrón comando y veamos las ventajas que nos ofrece, como por ejemplo, encapsular cómo se realizan las sentencias en cada motor de base de datos.

Empecemos por definir una clase sencilla para el Empleado.

/**
* POJO class to store employee details
*/
public class Employee {
    private String id;
    private String name;
    private String email;

    // set and get methods...
}

Es momento de crear los dos recibidores, uno para Oracle y otro para MSSQL Server. Podemos crear una interfaz para definir los métodos para ambos objetos.

/**
 * Receiver interface used to wrap all receivers types
 */
public interface ReceiverType {
    /**
    * Method that performs the actual action to query the database
    * @param query The query statement to execute
    * @param parameters The query parameters
    * @return Employee that matches the parameters
    */
    public Employee query(String query, Object[] parameters);
}

/**
 * OracleReceiver class used to perform Oracle queries
 */
public class OracleReceiver implements ReceiverType {
    @Override
    public Employee query(String query, Object[] parameters) {
        System.out.println("Connecting to Oracle database");
        System.out.println("Executing query: " + query + " > using ID=" + parameters[0]);
        return new Employee("1", "John", "john@email.com");
    }
}
/**
 * MSSQLReceiver class used to perform MSSQL queries
 */
public class MSSQLReceiver implements ReceiverType {
    @Override
    public Employee query(String query, Object[] parameters) {
        System.out.println("Connecting to MSSQL database");
        System.out.println("Executing query: " + query + " > using ID=" + parameters[0]);
        return new Employee("1", "Max", "max@email.com");
    }
}

Ahora creamos la interfaz para etiquetar los objetos de tipo Comando. El método execute recibe los parámetros a utilizar para buscar el empleado, en este caso sería el valor del ID.

/**
 * DatabaseCommand interface used to wrap all commands
 */
public interface DatabaseCommand {
    /**
    * Execute the command to get the Employee details
    * @param parameters Parameters used in the query
    * @return Employee details according to parameters provided
    */
    public Employee execute(Object[] parameters);
}

Vamos a implementar la interfaz usando dos Comandos concretos: OracleCommand y MSSQLCommand.

/**
 * OracleCommand class used to encapsulate how Oracle handles queries
 */
public class OracleCommand implements DatabaseCommand {
    /**
    * Variable to store the receiver details
    */
    private ReceiverType receiver;

    /**
    * Overloaded constructor to set the receiver for the command
    * @param _receiver ReceiverType to tie to the command
    */
    public OracleCommand(ReceiverType _receiver) {
        this.reciever = _receiver;
    }

    @Override
    public Employee execute(Object[] parameters) {
        String query = "select statement for Oracle";
        return receiver.query(query, parameters);
    }
}

/**
 * MSSQLCommand class used to encapsulate how MSSQL handles queries
 */
public class MSSQLCommand implements DatabaseCommand {
    /**
    * Variable to store the receiver details
    */
    private ReceiverType receiver;

    /**
    * Overloaded constructor to set the receiver for the command
    * @param _receiver ReceiverType to tie to the command
    */
    public MSSQLCommand(ReceiverType _receiver) {
        this.reciever = _receiver;
    }

    @Override
    public Employee execute(Object[] parameters) {
        String query = "select statement for MSSQL";
        return receiver.query(query, parameters);
    }
}

Podemos crear al invocador ahora que tenemos las otras partes configuradas.

/**
 * DBInvoker class used to handle commands references
 */
public class DBInvoker {
    /**
    * Variable to store the current command
    */
    private DatabaseCommand dbCommand;

    /**
    * Set the specific command to execute
    * @param dbCommand The command to execute
    */
    public void setDbCommand(DatabaseCommand _dbCommand) {
        this.dbCommand = _dbCommand;
    }

    /**
    * Method that executes the action for the current command
    * @param parameters Command parameters used in the statement
    * @return Employee that matches the parameters
    */
    public Employee execute(Object[] parameters) {
        return dbCommand.execute(parameters);
    }
}

Para finalizar, nuestro cliente se encarga de crear al invocador y ejecutar la acción necesaria.

public static void main(String[] args) {
    // create the invoker
    DBInvoker invoker = new DBInvoker();
    // set database query parameters
    Object[] parameters = {"1"};
    // create the specific receiver, oracle in this case
    ReceiverType receiverOracle = new OracleReceiver();
    // create oracle database command and send the receiver
    DatabaseCommand oracleCommand = new OracleCommand(receiverOracle);
    // set the command to execute against Oracle
    invoker.setDbCommand(oracleCommand);
    // get the result
    Employee employee = invoker.execute(parameters);
    System.out.println(employee);

    // create the specific receiver, mssql in this case
    ReceiverType receiverMS = new MSSQLReceiver();
    // create mssql database command and sent the receiver
    DatabaseCommand msCommand = new MSSQLCommand(receiverMS);
    // set the command to execute against MSSQL
    invoker.setDbCommand(msCommand);
    // get the result
    employee = invoker.execute(parameters);
    System.out.println(employee);
}

El resultado de ejecutar la aplicación es el siguiente.

Resultado de ejecutar el código de ejemplo

Este ejemplo demuestra como el patrón comando logra separar las acciones de los posibles clientes por medio del encapsulamiento de los métodos.

Dentro de las ventajas que ofrece este patrón esta la posibilidad de almacenar varios comandos en una estructura como una cola, para luego ser procesados por varios consumidores.

Por ejemplo, podríamos crear una serie de comandos necesarios para realizar diferentes actividades y almacenarlos todos en una cola, luego creamos varios consumidores (hilos) que se encargan de obtener el comando de la cola y ejecutarlo.

Ya que hay independencia entre los clientes y los comandos, estos hilos pueden estar distribuidos en diferentes servidores, esperando a que los comandos lleguen a la cola para ser ejecutados.

El código de ejemplo del patrón comando lo pueden descargar aquí.

En el próximo post vamos a analizar el patrón Adaptador.

Recordá suscribirte aquí para recibir las últimas actualizaciones todas las semanas.

Referencias:
Libro Head First Design Patterns. Versión digital.

Leave a Reply

Your email address will not be published. Required fields are marked *