Saltar al contenido principal

Command

BehavioralGang of FourAlrededor de 4 min

También conocido como

  • Action
  • Transaction

Propósito

El patrón de diseño Command encapsula una petición como un objeto, permitiendo así la parametrización de clientes con colas, peticiones y operaciones. También permite soportar operaciones deshechas.

Explicación

Ejemplo real

Hay un mago lanzando hechizos sobre un goblin. Los hechizos se ejecutan sobre el duende uno a uno. El primer hechizo encoge al duende y el segundo lo hace invisible. A continuación, el mago invierte los hechizos uno a uno. Cada hechizo es un objeto de comando que se puede deshacer.

En palabras simples:

Almacenar peticiones como objetos de comando permite realizar una acción o deshacerla en un momento posterior.

Wikipedia dice:

En programación orientada a objetos, el patrón de comandos es un patrón de diseño de comportamiento en el que un objeto se utiliza para encapsular toda la información necesaria para realizar una acción o desencadenar un evento en un momento posterior.

Ejemplo programático

Aquí está el código de ejemplo con mago Wizard y duende Goblin. Empecemos por la clase Mago Wizard.


@Slf4j
public class Wizard {

    private final Deque<Runnable> undoStack = new LinkedList<>();
    private final Deque<Runnable> redoStack = new LinkedList<>();

    public Wizard() {
    }

    public void castSpell(Runnable runnable) {
        runnable.run();
        undoStack.offerLast(runnable);
    }

    public void undoLastSpell() {
        if (!undoStack.isEmpty()) {
            var previousSpell = undoStack.pollLast();
            redoStack.offerLast(previousSpell);
            previousSpell.run();
        }
    }

    public void redoLastSpell() {
        if (!redoStack.isEmpty()) {
            var previousSpell = redoStack.pollLast();
            undoStack.offerLast(previousSpell);
            previousSpell.run();
        }
    }

    @Override
    public String toString() {
        return "Wizard";
    }
}

A continuación, tenemos al duende Goblin que es el objetivo Target de los hechizos.


@Slf4j
public abstract class Target {

    private Size size;

    private Visibility visibility;

    public Size getSize() {
        return size;
    }

    public void setSize(Size size) {
        this.size = size;
    }

    public Visibility getVisibility() {
        return visibility;
    }

    public void setVisibility(Visibility visibility) {
        this.visibility = visibility;
    }

    @Override
    public abstract String toString();

    public void printStatus() {
        LOGGER.info("{}, [size={}] [visibility={}]", this, getSize(), getVisibility());
    }
}

public class Goblin extends Target {

    public Goblin() {
        setSize(Size.NORMAL);
        setVisibility(Visibility.VISIBLE);
    }

    @Override
    public String toString() {
        return "Goblin";
    }

    public void changeSize() {
        var oldSize = getSize() == Size.NORMAL ? Size.SMALL : Size.NORMAL;
        setSize(oldSize);
    }

    public void changeVisibility() {
        var visible = getVisibility() == Visibility.INVISIBLE
                ? Visibility.VISIBLE : Visibility.INVISIBLE;
        setVisibility(visible);
    }
}

Por último, tenemos al mago en la función principal lanzando hechizos.

public static void main(String[]args){
        var wizard=new Wizard();
        var goblin=new Goblin();

        // casts shrink/unshrink spell
        wizard.castSpell(goblin::changeSize);

        // casts visible/invisible spell
        wizard.castSpell(goblin::changeVisibility);

        // undo and redo casts
        wizard.undoLastSpell();
        wizard.redoLastSpell();

Este es el ejemplo en acción.

var wizard=new Wizard();
        var goblin=new Goblin();

        goblin.printStatus();
        wizard.castSpell(goblin::changeSize);
        goblin.printStatus();

        wizard.castSpell(goblin::changeVisibility);
        goblin.printStatus();

        wizard.undoLastSpell();
        goblin.printStatus();

        wizard.undoLastSpell();
        goblin.printStatus();

        wizard.redoLastSpell();
        goblin.printStatus();

        wizard.redoLastSpell();
        goblin.printStatus();

Aquí está la salida del programa:

Goblin,[size=normal][visibility=visible]
        Goblin,[size=small][visibility=visible]
        Goblin,[size=small][visibility=invisible]
        Goblin,[size=small][visibility=visible]
        Goblin,[size=normal][visibility=visible]
        Goblin,[size=small][visibility=visible]
        Goblin,[size=small][visibility=invisible]

Diagrama de clases

alt text
Command

Aplicabilidad

Utilice el patrón Comando (Command) para:

  • Parametrizar objetos mediante una acción a realizar. Puedes expresar dicha parametrización en un lenguaje procedimental con una función callback, es decir, una función que se registra en algún lugar para ser llamada en un momento posterior. Los comandos son un sustituto orientado a objetos de las retrollamadas.
  • Especifican, ponen en cola y ejecutan peticiones en diferentes momentos. Un objeto Command puede tener una vida independiente de la petición original. Si el receptor de una petición puede ser representado de una manera independiente del espacio de direcciones, entonces puedes transferir un objeto comando para la petición a un proceso diferente y cumplir la petición allí.
  • Soporta deshacer. La operación de ejecución del comando puede almacenar el estado para revertir sus efectos en el propio comando. La interfaz del Comando debe tener una operación añadida de des-ejecutar que revierta los efectos de una llamada previa a ejecutar. Los comandos ejecutados se almacenan en una lista de historial. La funcionalidad de deshacer y rehacer a nivel ilimitado se consigue recorriendo esta lista hacia atrás y hacia delante llamando a un-ejecutar y ejecutar, respectivamente.
  • Soporta el registro de cambios para que puedan volver a aplicarse en caso de caída del sistema. Al aumentar la interfaz de comandos con operaciones de carga y almacenamiento, puede mantener un registro persistente de los cambios. La recuperación de un fallo implica volver a cargar los comandos registrados desde el disco y volver a ejecutarlos con la operación de ejecución.
  • Estructurar un sistema en torno a operaciones de alto nivel construidas sobre operaciones primitivas. Esta estructura es común en los sistemas de información que admiten transacciones. Una transacción encapsula un conjunto de cambios de datos. El patrón Command ofrece una forma de modelar las transacciones. Los comandos tienen una interfaz común que permite invocar todas las transacciones de la misma manera. El patrón también facilita la ampliación del sistema con nuevas transacciones.
  • Mantener un historial de peticiones.
  • Implementar la funcionalidad de callback.
  • Implementar la funcionalidad de deshacer.

Usos conocidos

Consecuencias

Ventajas:

  • Desacopla el objeto que invoca la operación del que sabe cómo realizarla.
  • Es fácil añadir nuevos Comandos, porque no tienes que cambiar las clases existentes.
  • Puedes ensamblar un conjunto de comandos en un comando compuesto.

Desventajas:

  • Aumenta el número de clases para cada comando individual.
  • Puede complicar el diseño al añadir múltiples capas entre emisores y receptores.

Patrones Relacionados

Credits