Patrones de diseño. Command Pattern

Introducción

El patrón Command (command pattern) es un patrón de diseño en el que los objetos representan acciones que serán consumidas por algún consumidor.

Problema

El patrón Command se utiliza para resolver diversos tipos de problemas en desarrollo de software:

  • Usado como simplificación de llamadas. En ocasiones el número de parámetros de una función crece hasta hacerse inmanejable. El uso del patrón command permite agrupar los parámetros en un solo objeto que se pasará a la función.
  • Usado como cola de comandos. Encapsular cada comando en un objeto permite su procesamiento de forma secuencial en forma de cola, de forma que un proceso trabajador pueda ir sacando los trabajos de dicha cola.
  • Operaciones con histórico. Usando objetos como comando y manteniendo esos comandos podemos mantener un histórico de los comandos utilizados y, en caso necesario, revertirlos.
  • Generalización de comandos. En ocasiones el uso de comandos permite la abstracción de dicho comando de forma que el comando constituye una clase abstracta de la que heredan los comandos específicos y que permite que un determinado objeto procese comandos ajustados a un determinado esquema independientemente de lo que haga dicho parámetro.

Código de ejemplo

type CCommand = class
  public
    procedure Execute; virtual; abstract;
end;

type CUpdateTextCommand = class(CCommand)
  private
    m_text : string;
  public
    procedure Execute; override;
    constructor Create(text : string);
end;

type CCallExtLibrary = class(CCommand)
  public
    procedure Execute; override;
end;

CUpdateTextCommand.Execute;
begin
  MainForm.TextBox = m_text;
end;

CCallExtLibrary.Execute;
begin
  ExtLibrary.DoYourThing(1,'Parametro');
end;

procedure Thread1.ActualizarTextoTextBox(texto : string);
begin
  MainThread.ColaMensajes.Push(CUpdateTextCommand.Create(texto);
end;

procedure Thread1.LlamarLibreríaExterna;
begin
  MainThread.ColaMensajes.Push(CCallExtLibrary.Create);
end;

De esta forma encapsulamos cada uno de los parametros y el código del hilo principal podría ser

  while ColaMensajes.Count >; 0 do
  begin
    CCommand msg := ColaMensajes.Pop;
    msg.Execute;
  end;

Ejemplos reales

Trabajos de impresión

Un ejemplo muy común es su uso en trabajos de impresión encapsulando toda la información necesaria para la impresión, como por ejemplo el numero de copias, la impresora en la que imprimir, la información del tamaño de página, si se debe imprimir en blanco y negro o color, etc. Toda esa información, incluyendo quizá incluso la representación binaria de los datos a imprimir, puede pasarse al proceso de impresión o, más probablemente, introducirse en una cola de impresión de la cual una tarea irá leyendo los trabajos a imprimir.

Programa de dibujo

En los programas de dibujo es casi obligatoria la presencia de una opción para deshacer lo hecho, paso a paso, en previsión de posibles meteduras de pata. Aunque hay múltiples formas de implementarlo, una de ellas consiste en encapsular cada acción dentro de un objeto, de forma que el programa sea capaz de ir recorriendo dichos objetos en orden inverso e ir deshaciendo las operaciones llevadas a cabo.

Algunas operaciones pueden deshacerse aplicando la acción inversa mientras que otras deberán contener (en este caso) los datos necesarios para devolver la imagen a su estado anterior (por ejemplo los bits que han cambiado desde un estado al anterior y como).

Procesador de ordenes por el hilo principal

En determinados lenguajes (por ejemplo Delphi) o debido al diseño de algunas librerías de terceros existen algunas limitaciones en la metodología multihilo, de forma que algunas llamadas es necesario que sean realizadas por el hilo principal de la aplicación. En estos casos el patrón Command nos permite encapsular cualquier tipo de comando que queramos realizar y apilarlo en una cola de órdenes para procesar por parte del hilo principal cuando tenga tiempo (ver el ejemplo de código).

Simplificando el API

Aunque esto no es un ejemplo de uso real específico podemos observar las diferencias entre un API simplificada con un patrón command y un API que no lo está, así como la mayor legibilidad del primero.

public void Print(int printHandle, bool blancoYNegro,
                        TTamPagina pag, int numCopias,
                        int pagInicio, int pagFin,
                        byte[] data);

plublic void ImprimeArchivo(string file)
{
  Print(Printers.Default, true, TTamPagina.A3,
         1,1,23, NombreABytes(file));
}
public void Print(CPrintJob Trabajo);

public void ImprimerArchivo(strin file)
{
  CPrintJob j = new DefaultJob();
  j.data = NombreABytes(file);
  j.tamPag = TTamPagina.A3;
  Print(j);
}

Se observa a simple vista que el segundo código es más fácil de leer y entender. El uso de una estructura (clase) para mantener la información nos da la posibilidad de simplificar el paso de parámetros por defecto y hace más evidente qué estamos pasando al mostrar de forma directa los nombres de los parámetros.

7.8
Average: 7.8 (5 votes)
Your rating: None