Patrones de Comportamiento (II): Patrón Command


Objetivo:

«Encapsular una petición como un objeto, de modo que puedan parametrizarse otros objetos con distintas peticiones o colas de peticiones y proporcionar soporte para realizar operaciones que puedan deshacerse».

Design Patterns: Elements of Reusable Object-Oriented Software

Cuando decimos que un patrón es «estructural» es sencillo imaginar que su cometido será modelar las clases de tal forma que cumplan un cometido concreto. Sin embargo, el concepto de diseñar clases orientando su funcionalidad hacia el comportamiento es algo más complicado. El patrón command u orden (como decía mi profesor de programación en primero de carrera, command se debería traducir por órden, no por comando, ya que un comando es un señor militar que pega tiros en la selva) tiene como objetivo encapsular la invocación de un método. Dicho así sin más, suena sencillo de entender pero… ¿cómo lo implementamos? La definición, a diferencia del patrón anterior, es un conglomerado de términos incomprensibles, ¿verdad? Encapsular una petición como un objeto. Vale, lo pillo. Pero ¿parametrizarse? ¿Soporte para realizar peticiones que puedan deshacerse?

Entendiendo el patrón Command

A estas alturas ya deberíamos de estar saturados del concepto de depender de abstracciones, no de concreciones. Y por ello, seguramente nos sea mucho más sencillo entender este patrón si lo adaptamos a un lenguaje más técnico y algo menos abstracto (principal objetivo de esta serie de artículos). Si hacemos aterrizar este concepto al mundo real, podríamos traducirlo como lo siguiente:

«Utilizar una interfaz con un método genérico de modo que los objetos que la implementen incluyan una referencia al objeto cuyo método queremos abstraer. Al invocar el método genérico se invocará este método, por lo que el objeto receptor no tiene por qué conocer el objeto original, bastándole una referencia a la interfaz para ejecutar el método«.

¿Mejor así? Quizás sea todavía un poco complicado, ¿verdad? Intentemos desgranarlo un poco más.

Si tenemos una interfaz común ICommand con un método genérico execute(), cualquier objeto que la implemente podrá añadir dentro del código de execute() la llamada al método que queremos abstraer. De este modo, el objeto receptor queda completamente desacoplado del objeto invocado. Le bastará con invocar ICommand.execute() para que ocurra la magia. El cliente será el encargado de implementar las clases adecuadas para asegurar la funcionalidad que el objeto receptor espera. Nuestro objeto receptor puede ser una aplicación web esperando ejecutar una operación sobre una base de datos. Lo ideal sería que nuestra aplicación no tuviera que preocuparse ni qué objeto usar para realizar la operación ni el nombre de ésta. Si la interfaz ICommand proporciona un método genérico para realizar esta operación, nuestros programadores podrán implementar tantos conectores a distintas bases de datos como quieran. Nuestra aplicación simplemente dirá algo así como «a mí no me cuentes tu vida. No me interesa saber qué hay más allá, yo ejecutaré el método ICommand.execute() y tú te encargas del resto del trabajo«.

Por lo tanto, podemos afirmar que el patrón Command es la quintaesencia de una interfaz: ofrece la firma de un método genérico que las clases que la implementan se encargarán de completar. Resumiendo, el patrón Command hace que la implementación del método de una interfaz encapsule la invocación del método de otro objeto distinto.

Si nos centramos en el diagrama de clases del diseño del patrón, podremos identificar los siguientes actores:

  • ICommand: interfaz que expone el método genérico (execute()).
  • CommandGenerico: implementa ICommand y posee una referencia al objeto cuyo método execute() tendrá que encapsular. Este objeto recibe el nombre de Receiver.
  • Receiver: como acabamos de decir, es el objeto que implementa la funcionalidad real. Alguno de sus métodos será encapsulado por ICommand.execute().
  • Invoker: clase encargada de invocar el método ICommand.execute(). Posee una referencia (o varias) a ICommand, y su método SetCommand le permite cambiar su funcionalidad en tiempo de ejecución. Ofrecerá también un método que invoque el método ICommand.Execute() que, a su vez, invocará Receiver.MetodoAEncapsular().

Implementando el patrón Command

Volvamos a nuestra fábrica de vehículos. Se nos ha encargado programar parte de la centralita de un nuevo modelo, concretamente de la activación y desactivación de las luces. Sabemos que contaremos con tres tipos de luces: posición, cortas y largas, pero la implementación de los métodos encargados de este proceso aún no están definidas, ya que se han subcontratado a una empresa coreana. Sin embargo, nuestro cliente nos exige que, pese a ello, debemos avanzar con el diseño de este módulo.

El patrón Command es perfecto en este escenario: contamos con un entorno en el que debemos ejecutar un método que aún no conocemos o que puede cambiar con el tiempo, por lo que crearemos una interfaz ICommand que exponga un método Execute(). Posteriormente, una vez que los requisitos estén claros y que sepamos qué hay que ejecutar, implementaremos clases que se acoplen entre nuestra centralita y el componente que la empresa coreana nos proporcionará.

Por lo tanto, comenzaremos creando una interfaz ICommand que se encargará de ofrecer una firma para el método que encapsulará el resto de los métodos: Execute():

    public interface ICommand
    {
        // El método Execute() será aquel que el objeto que reciba la referencia
        // será capaz de ejecutar.
        void Execute();
    }

A continuación modelaremos las clases que serán encapsuladas por el objeto Command. O más específicamente, las clases cuyos métodos serán encapsulados por el método Execute() del objecto Command(). Crearemos una clase abstracta de la que heredarán el resto de las clases que serán encapsuladas, aunque en realidad esto no es estrictamente necesario (un objeto Command podría albergar diversos objetos no necesariamente relacionados entre sí). Esta clase es conocida como Receiver, y se caracteriza porque se encargará de albergar la lógica concreta del método. Si nos ceñimos a la descripción de una interfaz, la clase Receiver sería aquella clase que se encargaría de implementarla.

LucesReceiver.cs

    // Clase abstracta de la que heredarán las clases que serán encapsuladas por los
    // objectos Command. Por lo tanto, sus métodos serán aquellos que encapsulará el
    // método Execute().
    public abstract class LucesReceiver
    {
        protected bool encendidas;
        protected int distanciaAlumbrado;

        // Propiedad de sólo lectura que devolverá el estado de las luces
        public bool Encendidas
        {
            get {return encendidas;}
        }

        // Método encargado de apagar las luces. Establece el estado del atributo 'encendidas'
        // a 'false'. Será común a todas las clases que hereden de ella.
        public void Apagar()
        {
            this.encendidas = false;
        }

        // El método Encender será distinto en cada una de las clases que hereden de esta clase.
        public abstract int Encender();
    }

A continuación implementaremos las clases concretas cuyos métodos serán encapsulados por ICommand.Execute(). Dado que heredan de la clase abstracta LucesReceiver, únicamente tendremos que implementar de forma explícita el método Encender(), ya que el resto de la funcionalidad será común. Comenzaremos por las luces de posición:

LucesPosicion.cs

    public class LucesPosicion : LucesReceiver
    {
        private const int DISTANCIA = 1;

        public override int Encender()
        {
            this.encendidas = true;
            return DISTANCIA;
        }
    }

A continuación hacemos lo mismo con las luces cortas.

LucesCortas.cs

    public class LucesCortas : LucesReceiver
    {
        private const int DISTANCIA = 40;

        public override int Encender()
        {
            this.encendidas = true;
            return DISTANCIA;
        }
    }

Finalmente, realizamos el mismo proceso con las luces largas.

LucesLargas.cs

    public class LucesLargas : LucesReceiver
    {
        private const int DISTANCIA = 200;

        public override int Encender()
        {
            this.encendidas = true;
            return DISTANCIA;
        }
    }

Esto se resumiría ahora mismo en el siguiente esquema:

Ya tenemos implementado uno de los elementos de nuestro patrón. Ahora haremos lo propio con la parte cliente, es decir, nuestra centralita. O más concretamente, el módulo encargado de encender y apagar las luces. Este se corresponderá con el elemento Invoker, que es aquel que tendrá como objetivo invocar la acción encapsulada por el objeto Command. Comenzaremos implementando una interfaz que ofrezca dos operaciones:

  • Una operación SetCommand(ICommand) que permitirá a nuestro módulo cambiar el ICommand a ejecutar.
  • Una operación Invoke() que invoque el método ICommand.Execute() que esté asignado en el momento actual.

IInvoker.cs

    public interface IInvoker
    {
        // Inyecta un ICommand, permitiendo cambiar la operación encapsulada en
        // tiempo de ejecución
        void SetCommand(ICommand command);

        // Ejecuta el método encapsulado
        void Invoke();
    } 

A continuación implementaremos la interfaz. Lo haremos a través de la clase ControladorLucesInvoker. Nuestra clase realizará justo aquello que acabamos de definir en la interfaz. Además, incluirá una referencia a un objeto que implemente la interfaz ICommand

    public class ControladorLucesInvoker : IInvoker
    {
        ICommand command;

        public void SetCommand(ICommand command)
        {
            this.command = command;
        }

        public void Invoke()
        {
            command.Execute();
        }
    }

Ya casi hemos completado nuestro patrón. Falta lo más importante: las clases que implementan la clase ICommand y que simbolizan las operaciones que se quieren encapsular. Estas operaciones serán, como podemos imaginar, encender y apagar. Por lo tanto, crearemos las clases EncenderLucesCommand
y ApagarLucesCommand:

EncenderLucesCommand.cs

    public class EncenderLucesCommand : ICommand
    {
        // Referencia al objeto cuyo método se quiere encapsular
        private LucesReceiver luces;

        // El constructor inyectará el objeto cuyo método se quiere encapsular
        public EncenderLucesCommand(LucesReceiver luces)
        {
            this.luces = luces;
        }

        // El método Execute() ejecutará el método encapsulado
        public void Execute()
        {
            int distancia = luces.Encender();
            Console.WriteLine(string.Format("Encendiendo luces. Alumbrando a una distancia de {0} metros.", distancia));
        }
    }

La clase ApagarLucesCommand realizará la operación contraria.

ApagarLucesCommand.cs

    public class ApagarLucesCommand : ICommand
    {
        private LucesReceiver luces;

        public ApagarLucesCommand(LucesReceiver luces)
        {
            this.luces = luces;
        }

        public void Execute()
        {
            luces.Apagar();
            Console.WriteLine("Apagando las luces");
        }
    }

Esto completará nuestro diagrama de clases, dejándolo de la siguiente manera:

Finalmente, añadimos el código que hará uso del patrón que acabamos de implementar. En él crearemos los tres tipos de luces (posición, cortas y largas) y utilizaremos el método SetCommand() de cada uno de los objetos destinados a encender y a apagar las luces para cambiar en tiempo de ejecución el tipo de luz a manejar.

            // Instanciamos los objetos cuyos métodos serán encapsulados dentro de
            // objetos que implementan ICommand
            LucesReceiver lucesPosicion = new LucesPosicion();
            LucesReceiver lucesCortas = new LucesCortas();
            LucesReceiver lucesLargas = new LucesLargas();

            // Creamos los objetos Command que encapsulan los métodos de las clases anteriores
            ICommand encenderPosicion = new EncenderLucesCommand(lucesPosicion);
            ICommand apagarPosicion = new ApagarLucesCommand(lucesPosicion);

            ICommand encenderCortas = new EncenderLucesCommand(lucesCortas);
            ICommand apagarCortas = new ApagarLucesCommand(lucesCortas);

            ICommand encenderLargas = new EncenderLucesCommand(lucesLargas);
            ICommand apagarLargas = new ApagarLucesCommand(lucesLargas);

            // Creamos un nuevo Invoker (el objeto que será desacoplado de las luces)
            IInvoker invoker = new ControladorLucesInvoker();

            // Le asociamos los objetos Command y los ejecutamos
            // El objeto invoker invoca el método 'Execute()' sin saber en ningún momento
            // qué es lo que se está ejecutando realmente.
            invoker.SetCommand(encenderPosicion);      // Asociamos el ICommand
            invoker.Invoke();                          // Hacemos que se ejecute ICommand.Execute()

            // Realizamos lo mismo con el resto de instancias que implementan ICommand.
            // Como podemos ver, el ICommand puede asignarse en tiempo de ejecucion
            invoker.SetCommand(apagarPosicion);
            invoker.Invoke();

            // Luces cortas
            invoker.SetCommand(encenderCortas);
            invoker.Invoke();

            invoker.SetCommand(apagarCortas);
            invoker.Invoke();

            // Luces largas
            invoker.SetCommand(encenderLargas);
            invoker.Invoke();

            invoker.SetCommand(apagarLargas);
            invoker.Invoke();

El resultado de ejecutar este código será el siguiente:

¿Cuándo utilizar este patrón? Ejemplos reales

Dado que este patrón nos permite encapsular la ejecución de un método como un objeto, una de las funcionalidades que nos ofrece este patrón es la de encadenar invocaciones. Si creamos una serie de objetos de este tipo y los almacenamos como objetos, éstos pueden pasarse como parámetros a un método que los vaya ejecutando de forma secuencial.

El segundo escenario en el que este patrón es útil es aquel que hemos visto en el ejemplo, es decir, aquel en el que el invocador del método deba ser desacoplado del objeto que maneja la invocación. Esto puede darse por necesidades del diseño o bien porque las especificaciones del módulo que maneja la invocación no estén definidas o existe previsión de que vaya a variar con el tiempo.

Recordemos además que la definición del patrón establecía que debía proporcionar soporte para deshacer operaciones. ¿Cómo realizar esto? Dado que estas operaciones se encapsulan como objetos, sería perfectamente posible mantener en memoria una pila con elementos ejecutados y otra que almacene objetos que modelen los distintos estados correspondientes a los instantes anteriores a ejecutar cada uno de los Commands de la primera pila. De este modo, realizar la opción deshacer sería tan sencillo como realizar un pop del primer objeto de cada pila y sobreescribir el estado actual del programa con el contenido del primer objeto almacenado en la pila de estados.

Otros ejemplos reales de este patrón serían, por ejemplo:

  • Grabación de macros (secuencia de operaciones)
  • Asistentes de instalación (cada pantalla implementaría un Command que realizaría una operación Execute() al pulsar el botón «Siguiente»)
  • Elementos Action del framework Swing de Java.

Fuentes:

5 comentarios

  1. Hola Daniel,

    Cuando he comenzado a leer la descripción de este patrón me ha resultado familiar y a medida que iba leyendo se han confirmado mis sospechas. La idea la he entendido perfectamente porque en proyectos WPF + patrón MVVM hay que implementar este patrón «comando – botón» que presentan las vistas.

    En este caso:

    – ICommand (clase nativa .net) = ICommand.
    – Clase RelayCommand = EncenderLucesCommand o ApagarLucesCommand.
    – Action (clase nativa .net) = ControladorLucesInvoker.
    – Clases abstractas = no aplica.

    Ahora todo encaja mejor. 🙂

    Gracias por el post.

    1. Hola, José Miguel.

      Los patrones de diseño conviven con nosotros. Aunque no los percibamos, están ahí. Y lo cierto es que cuando se «caza» la aplicación de uno de ellos en un entorno que nos sea familiar es bastante reconfortante. Nos damos cuenta de que tanta teoría es en realidad aplicada día a día 🙂
      Tal y como dices, las clases abstractas que incluyo en mi ejemplo «no aplican» en el modelo que comentas, pero en realidad no es necesario: lo que es necesario respetar es el núcleo del patrón, es decir, su concepto (tal y como veíamos con el método MoveNext() en el patrón Iterator).

      En el libro Head First: Design Patterns exponen un ejemplo de «deshacer una acción» a través de un método undo() encapsulado dentro del propio Command. Yo lo he planteado como una estructura con dos pilas. La implementación es distinta, pero a fin de cuentas, lo importante es entender el concepto de diseño.

      Muchas gracias por leerme y por tus comentarios.

      Un saludo 😉

  2. Hola Daniel, he leído la explicación y más o menos me ha quedado claro. Pero me gustaría hacerte unas preguntas y a ver si me las aclaras.

    En clase estamos haciendo una práctica de una calculadora [normal y científica para poder hacer cosas como pasar de binario a decimal y todo eso {con Java (Java Swing)}]. Y nos ha dicho que podemos hacer este patrón para que quede mejor. Y he aquí lo que yo he hecho:

    Tengo el ICommand con su método execute().
    Tengo la clase Calculadora con las funciones varias (sumar, potencia, pasar a decimal…) que creo que sería la clase Receiver.
    La clase Invoker que tiene su método invoker() y su constructor que le llega un ICommand.

    Pero tengo que hacer una clase por cada método que tengo el la clase Calculadora(Receiver)? es decir:

    class Suma implements ICommand
    class Potencia implements ICommand

    Y posteriormente crear una instancia de cada clase pasando le el objeto Calculadora que sería el Receiver.

    Sería necesario hacer este patrón.

    Un saludo y gracias.

  3. Buen día Daniel, una pregunta, ¿por qué utilizar la implementación de Invoker (ControladorLuces) que es muy parecida con la implementación de ICommand (EncenderLuces), es decir, ambas inyectan un objeto e implementan la ejecución de LucesReceiver, LucesPosicion, LucesCortas y LucesLargas?

Deja un comentario