Un Command es un objeto que encapsula una petición y la información necesaria para ejecutarla. Así podemos pasarla, almacenarla o programarla como cualquier otro dato.
Piensa en la comanda de un restaurante. El cliente hace un pedido, el camarero lo entrega y la cocina sabe cómo ejecutarlo.
En programación, a menudo acoplamos demasiado el botón que dispara una acción con la acción misma.
Si ponemos el código de “Guardar Archivo” dentro del evento Click del botón, tenemos un problema:
- No podemos reutilizar esa lógica en un atajo de teclado.
- No podemos poner esa acción en una cola para hacerla más tarde.
- No podemos deshacerla a menos que guardemos el estado anterior o una operación inversa.
El patrón Command nos propone encapsular una petición como un objeto independiente.
La estructura
El patrón separa cuatro actores principales:
Command (IComando): Interfaz que declara el método Execute().
Concrete Command (EncenderLuzComando): Implementa Execute. Tiene una referencia al objeto que realmente sabe hacer el trabajo (el Receiver).
Receiver (Luz): El objeto de negocio real (la bombilla, el documento, el motor).
Invoker (ControlRemoto): El que dispara la orden (el botón). No sabe qué hace el comando, solo sabe llamar a Execute().
:::
Implementación Básica en C#
Vamos a simular un Control Remoto Inteligente para domótica.
El Receptor (Receiver)
El objeto que hace el trabajo real.
public class Luz
{
public void Encender() => Console.WriteLine("💡 Luz Encendida");
public void Apagar() => Console.WriteLine("🌑 Luz Apagada");
}La Interfaz Command
public interface IComando
{
void Ejecutar();
}El Comando Concreto
Este objeto es el “enlace”. Guarda quién tiene que hacer algo (_luz) y qué tiene que hacer (Encender).
public class EncenderLuzComando : IComando
{
private Luz _luz;
public EncenderLuzComando(Luz luz)
{
_luz = luz;
}
public void Ejecutar()
{
_luz.Encender();
}
}El Invocador (Invoker)
El control remoto. Fíjate que recibe un IComando. Podría ser encender una luz, abrir el garaje o disparar un misil. Al control remoto le da igual.
public class ControlRemoto
{
private IComando _comando;
public void SetComando(IComando comando)
{
_comando = comando;
}
public void PresionarBoton()
{
_comando.Ejecutar();
}
}El “Killer Feature”: Deshacer (Undo)
Hasta aquí, el patrón parece añadir complejidad extra para hacer una simple llamada a un método. Pero empieza a tener sentido cuando añadimos el método Deshacer.
Como el objeto Comando sabe qué acción se ejecutó, también puede saber cómo revertirla.
Modificamos la interfaz:
public interface IComando
{
void Ejecutar();
void Deshacer(); // La operación inversa
}Y actualizamos el comando concreto. Si ejecutar es “Encender”, deshacer es “Apagar”.
public class EncenderLuzComando : IComando
{
private Luz _luz;
public EncenderLuzComando(Luz luz) => _luz = luz;
public void Ejecutar() => _luz.Encender();
public void Deshacer() => _luz.Apagar();
}Historial de Comandos
Ahora nuestro invocador puede tener memoria. Guardamos los comandos ejecutados en una Pila (Stack).
public class ControlRemotoAvanzado
{
private Stack<IComando> _historial = new Stack<IComando>();
public void EjecutarComando(IComando comando)
{
comando.Ejecutar();
_historial.Push(comando); // Guardamos lo que acabamos de hacer
}
public void PresionarBotonDeshacer()
{
if (_historial.Count > 0)
{
var ultimoComando = _historial.Pop();
Console.WriteLine("--- Deshaciendo última acción ---");
ultimoComando.Deshacer();
}
}
}Probando el sistema
class Program
{
static void Main(string[] args)
{
// Preparación
Luz luzSalon = new Luz();
IComando encender = new EncenderLuzComando(luzSalon);
// Invocador
ControlRemotoAvanzado mando = new ControlRemotoAvanzado();
// 1. Encendemos la luz
mando.EjecutarComando(encender);
// Salida: 💡 Luz Encendida
// 2. ¡Nos hemos equivocado! Ctrl+Z
mando.PresionarBotonDeshacer();
// Salida:
// --- Deshaciendo última acción ---
// 🌑 Luz Apagada
}
}En aplicaciones reales (como editores de texto), el comando suele guardar el estado previo del objeto antes de ejecutar la acción. Por ejemplo, si el comando es EscribirTexto, guarda el texto que había antes para poder restaurarlo al hacer Deshacer.
Otros usos potentes: Colas y Macros
Al ser objetos, los comandos se pueden meter en listas.
Macros (Composite Command)
Podemos crear un comando que contenga una lista de otros comandos. “Modo Fiesta” = Bajar persianas + Encender luces + Poner música.
public class MacroComando : IComando
{
private List<IComando> _comandos;
public MacroComando(List<IComando> comandos) => _comandos = comandos;
public void Ejecutar()
{
foreach (var cmd in _comandos) cmd.Ejecutar();
}
}Colas de Tareas (Job Queues)
Podemos serializar los comandos y guardarlos en una base de datos para ejecutarlos más tarde, o enviarlos a otro servidor. Esto es la base de sistemas como Hangfire o colas de mensajes.
Ventajas y Desventajas
| Ventajas | Desventajas |
|---|---|
| Desacoplamiento: El que pide la acción no conoce al que la ejecuta. | Explosión de Clases: Necesitas crear una clase nueva para cada acción (CopiarCmd, PegarCmd, CortarCmd…). |
| Extensibilidad: Undo/Redo, Logging, Macros, Transacciones. | Complejidad: Para un botón simple que solo imprime un mensaje, es matar moscas a cañonazos. |
Command resulta especialmente útil en aplicaciones con interacciones complejas (editores, juegos o herramientas de diseño) y en sistemas que encolan tareas.
Es el patrón que convierte la acción efímera de “hacer algo” en un objeto tangible que podemos guardar, deshacer y gestionar.