🎮 El patrón de diseño Command
El patrón de diseño Command es un patrón de comportamiento que toma una acción completa y la encapsula como un objeto. Suena abstracto, pero la idea es simple. Queremos separar la orden de quien la da.
En vez de que un botón llame directamente a editor.guardar(), el botón recibe un objeto que sabe cómo ejecutar esa acción. El botón solo tiene que pulsar play sin saber qué hay dentro.

🤯 1. ¿Qué resolvemos con el patrón de diseño Command?
Detectando un acoplamiento fuerte
Imagina que estás construyendo una aplicación de edición de texto. Tienes un botón para Guardar y otro para Imprimir.
El happy path es hacer que el botón llame directamente al método de la clase editor:
// Código acoplado y difícil de reutilizar
// Clase que realmente hace el trabajo (el Receptor)
class EditorTexto {
public function guardar(): void {
echo "Guardando el documento en disco..." . PHP_EOL;
}
public function imprimir(): void {
echo "Imprimiendo el documento..." . PHP_EOL;
}
}
// Clase que da la orden (el Invocador)
class Boton {
private EditorTexto $editor;
public function __construct(EditorTexto $editor) {
$this->editor = $editor;
}
// El Botón sabe EXACTAMENTE qué clase y método llamar
public function clickGuardar(): void {
$this->editor->guardar();
}
}
El problema es que el Boton está acoplado a la clase EditorTexto. Si mañana quieres que ese botón ejecute una
tarea completamente diferente, como enviar un email, tienes que modificar la clase Boton. Y si tienes 15 botones,
tienes 15 clases acopladas.
Necesitas un sistema donde quien pulsa el botón no sepa qué se va a ejecutar, solo que se ejecutará algo.
Una alternativa para solucionar este acoplamiento
El patrón de diseño Command introduce una capa intermedia: el Objeto Comando.
Este patrón consiste en convertir una petición en un objeto independiente. Este objeto lleva consigo toda la información necesaria para ejecutar la acción más tarde, sin que el invocador (el botón) tenga que saber quién o cómo se hace.
¿Cuál es la idea central del patrón Command?
🌎 2. El mando a distancia 📺
Imagina un control remoto de una televisión.
- El Invocador, en este caso, serían los diferentes botones del mando: Tú pulsas el botón de subir volumen. El botón es el mismo en todos los mandos y solo tiene una tarea: ejecutar. No sabe si el televisor está encendido o qué marca es.
- La Interfaz (el contrato de comando): La norma que dice: Todo lo que se considere una orden debe tener un método
ejecutar(). - El Comando Concreto (El objeto subir volumen): Es el chip dentro del mando que encapsula la orden. Contiene dos cosas: la referencia al Televisor (quien recibe la orden) y la acción de subir volumen.
- El Receptor (el televisor): Es la clase que realmente ejecuta la lógica (
$televisor->subirVolumen()).
El botón del mando solo llama a ComandoSubirVolumen->ejecutar(). De esta forma, puedes cambiar el Comando (por ejemplo, a un ComandoCambiarCanal) sin tener que cambiar el botón físico.
🛠️ 3. Los roles del patrón
Define el contrato: ejecutar() y opcionalmente deshacer(). Todos los comandos concretos deben implementarla.
Encapsula la acción y guarda una referencia al Receptor. Puede almacenar parámetros adicionales (como el formato de impresión).
La clase que realmente sabe hacer las cosas. Contiene la lógica de negocio: guardar, imprimir, enviar email...
Solo sabe que tiene un Comando y lo ejecuta. No conoce el detalle. Puede ser un botón, un menú, un atajo de teclado o un cron job.
La clave está en que el Invocador no depende de ninguna clase concreta. Solo depende de la interfaz Comando. Puedes darle cualquier comando y lo ejecutará sin rechistar.
¿Qué rol cumple el Invocador (BotonUI) en el patrón?
🧑💻 4. El patrón Command aplicado al ejemplo
Vamos a rehacer el ejemplo del editor de texto usando el patrón Command.
<?php
// 1. Interfaz de Comando (El Contrato)
// Define el método que todos los Comandos deben tener.
interface Comando {
public function ejecutar(): void;
}
// 2. Receptor (El que sabe hacer las cosas)
// La clase original que contiene la lógica.
class EditorTexto {
public function guardar(): void {
echo " [RECEPTOR] Documento guardado correctamente." . PHP_EOL;
}
public function imprimir(string $tipo): void {
echo " [RECEPTOR] Imprimiendo documento con formato: $tipo." . PHP_EOL;
}
}
// 3. Comando Concreto: Guardar (Encapsula la petición)
class ComandoGuardar implements Comando {
private EditorTexto $receptor;
// El comando se crea con la referencia al Receptor
public function __construct(EditorTexto $editor) {
$this->receptor = $editor;
}
// Aquí solo llama al método del Receptor.
public function ejecutar(): void {
echo "-> [COMANDO] Orden de Guardar recibida." . PHP_EOL;
$this->receptor->guardar();
}
}
// 4. Comando Concreto: Imprimir (Puede llevar parámetros)
class ComandoImprimir implements Comando {
private EditorTexto $receptor;
private string $formato;
public function __construct(EditorTexto $editor, string $formato) {
$this->receptor = $editor;
$this->formato = $formato;
}
public function ejecutar(): void {
echo "-> [COMANDO] Orden de Imprimir recibida." . PHP_EOL;
$this->receptor->imprimir($this->formato);
}
}
// 5. Invocador (El que da la orden)
// El Invocador solo conoce la interfaz genérica "Comando".
class BotonUI {
private Comando $comando;
// Se configura con el Comando que debe ejecutar.
public function setComando(Comando $c): void {
$this->comando = $c;
echo "--- Botón configurado para: " . get_class($c) . " ---" . PHP_EOL;
}
// El botón ejecuta el Comando, sin saber qué hay dentro.
public function click(): void {
echo "[INVOCADOR] Botón pulsado..." . PHP_EOL;
$this->comando->ejecutar();
}
}
// 🔹 6. Uso del patrón (Cliente)
$miEditor = new EditorTexto();
$botonGuardar = new BotonUI();
$botonImprimir = new BotonUI();
// 1. Configurar y usar el comando Guardar
$comandoGuardar = new ComandoGuardar($miEditor);
$botonGuardar->setComando($comandoGuardar);
$botonGuardar->click();
echo PHP_EOL;
// 2. Configurar y usar el comando Imprimir (con un parámetro)
$comandoImprimirPDF = new ComandoImprimir($miEditor, "PDF");
$botonImprimir->setComando($comandoImprimirPDF);
$botonImprimir->click();
// 🖥️ Resultado en consola:
// --- Botón configurado para: ComandoGuardar ---
// [INVOCADOR] Botón pulsado...
// -> [COMANDO] Orden de Guardar recibida.
// [RECEPTOR] Documento guardado correctamente.
//
// --- Botón configurado para: ComandoImprimir ---
// [INVOCADOR] Botón pulsado...
// -> [COMANDO] Orden de Imprimir recibida.
// [RECEPTOR] Imprimiendo documento con formato: PDF.
El BotonUI no sabe nada de EditorTexto. Solo conoce la interfaz Comando. Puedes darle un ComandoGuardar, un
ComandoImprimir o un ComandoEnviarEmail y funcionará igual.
🔄 5. El poder del Undo/Redo
Aquí es donde Command pasa de ser interesante a ser la p****. Como cada acción es un objeto, podemos añadir un método deshacer() a la interfaz y guardar un historial de acciones ejecutadas.
<?php
// Interfaz con soporte para Undo
interface ComandoReversible {
public function ejecutar(): void;
public function deshacer(): void;
}
// Receptor
class DocumentoTexto {
private string $contenido = '';
public function escribir(string $texto): void {
$this->contenido .= $texto;
}
public function borrarUltimos(int $cantidad): void {
$this->contenido = substr($this->contenido, 0, -$cantidad);
}
public function getContenido(): string {
return $this->contenido;
}
}
// Comando con soporte Undo
class ComandoEscribir implements ComandoReversible {
private DocumentoTexto $documento;
private string $texto;
public function __construct(DocumentoTexto $documento, string $texto) {
$this->documento = $documento;
$this->texto = $texto;
}
public function ejecutar(): void {
$this->documento->escribir($this->texto);
}
public function deshacer(): void {
// Borra exactamente los caracteres que escribió
$this->documento->borrarUltimos(strlen($this->texto));
}
}
// Invocador con historial de Undo/Redo
class EditorConHistorial {
/** @var ComandoReversible[] */
private array $historial = [];
/** @var ComandoReversible[] */
private array $pilaRehacer = [];
public function ejecutar(ComandoReversible $comando): void {
$comando->ejecutar();
$this->historial[] = $comando;
$this->pilaRehacer = []; // Al ejecutar algo nuevo, se limpia el redo
}
public function deshacer(): void {
if (empty($this->historial)) {
echo "Nada que deshacer\n";
return;
}
$comando = array_pop($this->historial);
$comando->deshacer();
$this->pilaRehacer[] = $comando;
}
public function rehacer(): void {
if (empty($this->pilaRehacer)) {
echo "Nada que rehacer\n";
return;
}
$comando = array_pop($this->pilaRehacer);
$comando->ejecutar();
$this->historial[] = $comando;
}
}
// Uso
$doc = new DocumentoTexto();
$editor = new EditorConHistorial();
$editor->ejecutar(new ComandoEscribir($doc, "Hola "));
$editor->ejecutar(new ComandoEscribir($doc, "mundo "));
$editor->ejecutar(new ComandoEscribir($doc, "cruel"));
echo $doc->getContenido() . "\n"; // "Hola mundo cruel"
$editor->deshacer();
echo $doc->getContenido() . "\n"; // "Hola mundo "
$editor->deshacer();
echo $doc->getContenido() . "\n"; // "Hola "
$editor->rehacer();
echo $doc->getContenido() . "\n"; // "Hola mundo "
Cada ComandoEscribir recuerda exactamente lo que añadió y sabe borrarlo. El EditorConHistorial solo apila y desapila
comandos. Ninguno sabe del otro más de lo necesario.
Cualquier editor de texto moderno (VS Code, Word, Photoshop…) usa este patrón internamente para el Ctrl+Z / Ctrl+Y. No es casualidad.
¿Cómo se implementa la funcionalidad Undo con el patrón Command?
🧩 6. MacroCommand: Comandos compuestos
Otra ventaja brutal de que las acciones sean objetos es que puedes agrupar varios comandos en uno solo. Es el patrón Command combinado con Composite:
class MacroComando implements Comando {
/** @var Comando[] */
private array $comandos = [];
public function agregar(Comando $comando): void {
$this->comandos[] = $comando;
}
public function ejecutar(): void {
foreach ($this->comandos as $comando) {
$comando->ejecutar();
}
}
}
// Uso: un botón "Guardar y Exportar" que ejecuta dos cosas
$editor = new EditorTexto();
$macro = new MacroComando();
$macro->agregar(new ComandoGuardar($editor));
$macro->agregar(new ComandoImprimir($editor, "PDF"));
$boton = new BotonUI();
$boton->setComando($macro);
$boton->click();
// Ejecuta guardar + imprimir con un solo click
Esto es muy útil para transacciones, flujos de trabajo o acciones compuestas. El BotonUI sigue sin saber nada,
simplemente llama a ejecutar() sobre algo que implementa Comando.
¿Qué es un MacroCommand?
📋 7. Caso real: Cola de tareas
Otro uso clásico es montar un Job Queue o cola de tareas. Como los comandos son objetos, puedes guardarlos en una lista y ejecutarlos después:
class ColaDeTareas {
/** @var Comando[] */
private array $cola = [];
public function encolar(Comando $comando): void {
$this->cola[] = $comando;
echo "📥 Tarea encolada: " . get_class($comando) . "\n";
}
public function procesarTodas(): void {
echo "🚀 Procesando " . count($this->cola) . " tareas...\n";
while (!empty($this->cola)) {
$comando = array_shift($this->cola);
$comando->ejecutar();
}
echo "✅ Todas las tareas procesadas\n";
}
}
// Uso
$editor = new EditorTexto();
$cola = new ColaDeTareas();
$cola->encolar(new ComandoGuardar($editor));
$cola->encolar(new ComandoImprimir($editor, "PDF"));
$cola->encolar(new ComandoImprimir($editor, "HTML"));
// Las tareas se ejecutan más tarde (o en otro hilo/proceso)
$cola->procesarTodas();
Esto es exactamente lo que hacen Laravel Queues, Symfony Messenger, RabbitMQ o cualquier sistema de colas. La idea es la misma: encapsular una acción como objeto, serializarla y ejecutarla después.
🔄 8. Comparativa con otros patrones
| Patrón | Propósito | Diferencia clave con Command |
|---|---|---|
| Command | Encapsular una acción como objeto | La acción es un objeto con estado que puede deshacerse, encolarse o loguearse |
| Strategy | Intercambiar algoritmos en runtime | Strategy cambia el cómo se hace algo, Command encapsula el qué se hace |
| Observer | Notificar a múltiples objetos de un evento | Observer es 1 a muchos y reactivo; Command es 1 a 1 y explícito |
| Chain of Responsibility | Pasar petición por una cadena de manejadores | En CoR no se sabe quién maneja la petición; en Command el receptor está definido |
| Memento | Guardar y restaurar el estado de un objeto | Memento guarda snapshots; Command guarda operaciones (más eficiente en memoria) |
La diferencia más importante entre Command y Strategy es sutil: un Strategy normalmente reemplaza a otro (solo tienes uno activo), mientras que los Commands se acumulan (tienes un historial de ellos).
¿Cuál es la diferencia principal entre Command y Strategy?
⚖️ 9. Relación con SOLID
Cada comando concreto encapsula una sola acción. ComandoGuardar solo guarda, ComandoImprimir solo imprime. Un solo motivo para cambiar.
Añadir un nuevo comando = crear una clase nueva. Sin tocar el Invocador ni los comandos existentes. ComandoEnviarEmail, ComandoExportar... lo que quieras.
El Invocador (BotonUI) depende de la abstracción Comando, no de ComandoGuardar ni de EditorTexto. Inversión de dependencia perfecta.
Cualquier Comando concreto puede usarse donde se espera la interfaz Comando. El Invocador funciona igual con ComandoGuardar, MacroComando o cualquier otra implementación.
✅ 10. Ventajas y desventajas
- Desacoplamiento total: El Invocador no sabe nada del Receptor.
- Undo/Redo: Cada acción es un objeto, puedes deshacerla y rehacerla.
- Cola de tareas: Serializa comandos y ejecútalos después (o en otro proceso).
- Logging: Guarda un historial completo de acciones ejecutadas.
- MacroCommands: Agrupa varias acciones en una sola.
- OCP: Añadir comandos nuevos sin tocar nada existente.
- Más clases: Cada acción nueva = una clase nueva. El código crece rápido.
- Complejidad: Para una acción simple sin Undo ni colas, es matar moscas a cañonazos.
- Parámetros inmutables: Los parámetros se fijan al crear el comando, no al ejecutarlo.
- Undo complejo: Deshacer operaciones que afectan a varios objetos puede ser difícil.
⚠️ 11. Errores comunes
1. El comando que hace demasiado
Si tu comando tiene lógica de negocio dentro, lo estás haciendo mal. El comando solo debe delegar al Receptor:
// ❌ MAL: el comando contiene lógica de negocio
class ComandoGuardar implements Comando {
public function ejecutar(): void {
$datos = $this->validarFormulario();
$this->conectarBaseDatos();
$this->insertarRegistro($datos);
$this->enviarNotificacion();
}
}
// ✅ BIEN: el comando solo delega
class ComandoGuardar implements Comando {
private EditorTexto $receptor;
public function ejecutar(): void {
$this->receptor->guardar();
}
}
El comando es un mensajero, no un trabajador. Si le pones lógica, rompes SRP y acoplas todo.
¿Por qué es un error que el Comando contenga lógica de negocio?
2. Undo sin guardar estado
Si implementas deshacer() pero no guardas el estado previo, no puedes deshacer nada:
// ❌ MAL: no guarda estado para deshacer
class ComandoBorrar implements ComandoReversible {
public function ejecutar(): void {
$this->receptor->borrarTodo();
}
public function deshacer(): void {
// ¿Y ahora qué restauro? No guardé nada 💀
}
}
// ✅ BIEN: guarda backup antes de ejecutar
class ComandoBorrar implements ComandoReversible {
private string $backup = '';
public function ejecutar(): void {
$this->backup = $this->receptor->getContenido();
$this->receptor->borrarTodo();
}
public function deshacer(): void {
$this->receptor->setContenido($this->backup);
}
}
3. Invocar directamente al Receptor
Si el cliente llama al Receptor sin pasar por el Comando, pierdes todo el beneficio del patrón:
// ❌ MAL: bypaseas el patrón
$editor->guardar(); // Directo, sin comando
// ✅ BIEN: siempre a través del comando
$boton->setComando(new ComandoGuardar($editor));
$boton->click();
Si haces bypass, no hay Undo, no hay logging, no hay cola. Pierdes todo lo que te da el patrón.
4. No limpiar la pila de Redo al ejecutar
Error sutil pero importante: cuando el usuario ejecuta una acción nueva después de haber hecho Undo, la pila de Redo debe limpiarse. Si no, puedes acabar rehaciendo acciones que ya no tienen sentido:
public function ejecutar(ComandoReversible $comando): void {
$comando->ejecutar();
$this->historial[] = $comando;
$this->pilaRehacer = []; // ← IMPORTANTE: limpiar redo
}
¿Qué debe pasar con la pila de Redo cuando se ejecuta una acción nueva después de hacer Undo?
🤔 12. ¿Cuándo usarlo y cuándo no?
- Necesitas Undo/Redo
- Quieres montar una cola de tareas o scheduler
- Necesitas un registro de actividad (audit log)
- El Invocador debe cambiar su comportamiento en runtime
- Quieres agrupar acciones en transacciones (MacroCommand)
- Necesitas serializar acciones (enviarlas por red o guardarlas en BD)
- Tu aplicación es muy simple y las acciones nunca cambian
- No necesitas Undo, colas ni logging
- Solo tienes una acción posible (no hay variación)
- El Invocador y el Receptor nunca cambiarán por separado
Si tienes un botón que llama directamente a
$editor->guardar()y nunca necesitarás Undo, cola ni logging, no metas Command. Pero si tu editor tiene 15 acciones y quieres Ctrl+Z… Command es tu amigo.
¿Cuándo NO deberías usar el patrón Command?
💡 13. Conclusión
El Command es uno de los patrones más versátiles de todo el cátalogo. Su idea central es potente y simple: una acción es un objeto. Y como objeto, puedes almacenarlo, pasarlo como parámetro, ponerlo en una cola, serializarlo, deshacerlo o rehacerlo.
Lo ves en todas partes: el Ctrl+Z de cualquier editor, las colas de Laravel/Symfony, los eventos de un videojuego, las transacciones de una base de datos… Donde hay acciones que necesitan ser tratadas como datos, hay un Command escondido.
Y recuerda: el Invocador no sabe qué hace el Comando, y el Comando no sabe quién lo invoca. Eso es desacoplamiento de verdad, nene.
EA, ¡saluditos y nos vemos en los bares! 🍻