Patrón Command
Encapsula una petición como un objeto, permitiendo parametrizar clientes con colas, logs y operaciones deshacibles. Patrón de diseño conductual.
Visión general
El Patrón Command es un patrón de diseño conductual que convierte una petición en un objeto independiente que contiene toda la información sobre la petición. Esto te permite parametrizar métodos con diferentes peticiones, retrasar o encolar ejecución y soportar operaciones deshacibles.
Es la base de sistemas de undo/redo, colas de trabajo, grabación de macros y operaciones transaccionales.
Cuándo usarlo
Usa el Patrón Command cuando:
- Necesitas parametrizar objetos con operaciones a ejecutar
- Quieres encolar, programar o ejecutar operaciones remotamente
- Necesitas funcionalidad de deshacer/rehacer
- Quieres registrar cambios para reproducción o auditoría
- Necesitas comportamiento transaccional (ejecutar todo o revertir)
Solución
Python
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class Light:
def __init__(self):
self.is_on = False
def turn_on(self):
self.is_on = True
print("Light is on")
def turn_off(self):
self.is_on = False
print("Light is off")
class TurnOnCommand(Command):
def __init__(self, light: Light):
self.light = light
def execute(self):
self.light.turn_on()
def undo(self):
self.light.turn_off()
# Uso
light = Light()
cmd = TurnOnCommand(light)
cmd.execute() # Light is on
cmd.undo() # Light is off
JavaScript
class Light {
constructor() {
this.isOn = false;
}
turnOn() {
this.isOn = true;
console.log("Light is on");
}
turnOff() {
this.isOn = false;
console.log("Light is off");
}
}
class TurnOnCommand {
constructor(light) {
this.light = light;
}
execute() {
this.light.turnOn();
}
undo() {
this.light.turnOff();
}
}
// Uso
const light = new Light();
const cmd = new TurnOnCommand(light);
cmd.execute(); // Light is on
cmd.undo(); // Light is off
Java
interface Command {
void execute();
void undo();
}
class Light {
boolean isOn = false;
void turnOn() { isOn = true; System.out.println("Light is on"); }
void turnOff() { isOn = false; System.out.println("Light is off"); }
}
class TurnOnCommand implements Command {
private final Light light;
TurnOnCommand(Light light) { this.light = light; }
public void execute() { light.turnOn(); }
public void undo() { light.turnOff(); }
}
// Uso
Light light = new Light();
Command cmd = new TurnOnCommand(light);
cmd.execute(); // Light is on
cmd.undo(); // Light is off
Explicación
El Patrón Command separa la invocación de la acción de su ejecución:
- Interfaz Command: Declara
execute()y opcionalmenteundo() - Command concreto (
TurnOnCommand): Vincula un receptor (Light) a una acción (turnOn) - Receptor (
Light): El objeto que realiza el trabajo real - Invocador: Llama
execute()en los commands (ej.
Al encapsular peticiones como objetos, ganas la habilidad de encolar, loggear y revertir operaciones.
Variantes
| Variante | Caso de uso | Compromiso |
|---|---|---|
| Command simple | Acción directa sin undo | Fácil de implementar, flexibilidad limitada |
| Command deshacible | Operaciones que pueden revertirse | Requiere mantener estado para la reversión |
| Macro Command | Compuesto de múltiples commands | Potente, pero más difícil de deshacer atómicamente |
Lo que funciona
- Implementa
undo()para cada command si tu sistema soporta deshacer - Mantén los commands sin estado cuando sea posible: Almacena estado del receptor, no del command
- Usa un historial de commands (stack) para soportar deshacer/rehacer multinivel
- Documenta efectos secundarios: Commands que afectan sistemas externos son más difíciles de deshacer
- Considera inmutabilidad: Una vez configurado, un command no debería cambiar su target
Errores comunes
- Olvidar estado de undo: Commands que no pueden revertirse rompen el stack de undo
- Acoplamiento fuerte: Commands que dependen de estado global en lugar de un receptor específico
- Sobre-ingeniería: Usar Command para operaciones triviales que nunca necesitan encolado o deshacer
- Suposiciones síncronas: No considerar que los commands pueden ejecutarse asíncronamente
- Falta de idempotencia: Ejecutar el mismo command dos veces produce resultados diferentes
- No manejar fallos de command: Commands que lanzan excepciones pueden dejar el sistema en un estado inconsistente
- Almacenar demasiado estado en commands: Los commands deberían ser ligeros; almacenar objetos grandes afecta memoria y serialización
- Ignorar seguridad de threads: Commands ejecutados concurrentemente pueden acceder recursos compartidos sin sincronización apropiada
- No loggear ejecución de command: Faltar trails de auditoría hace debugging y cumplimiento difícil
- Mezclar preocupaciones: Commands que realizan múltiples responsabilidades no relacionadas violan el principio de responsabilidad única
Mejores Prácticas
-
Mantén commands ligeros. Los commands deberían ser objetos pequeños y enfocados que encapsulan una sola acción. Evita almacenar grandes cantidades de datos en commands.
-
Implementa undo para todos los commands que cambian estado. Si tu sistema soporta deshacer, cada command que cambie estado debería implementar lógica de undo.
-
Usa factories de command para creación compleja. Cuando los commands requieren configuración compleja, usa métodos de factory o builders para encapsular lógica de creación.
-
Maneja excepciones gracefulmente. Los commands deberían manejar sus propias excepciones o proporcionar información de error clara al invocador.
-
Haz commands serializables. Si necesitas persistir commands o enviarlos por la red, asegúrate que puedan serializarse y deserializarse.
-
Documenta efectos secundarios de command. Documenta claramente cualquier efecto secundario externo que un command pueda tener, especialmente aquellos difíciles de deshacer.
-
Usa historial de command para debugging. Mantén un historial de commands ejecutados para ayudar con debugging y trails de auditoría.
-
Considera composición de command. Usa commands compuestos para construir operaciones complejas de commands más simples y reutilizables.
-
Valida parámetros de command. Valida parámetros de command antes de ejecución para fallar rápido y proporcionar mensajes de error claros.
-
Prueba commands en aislamiento. Escribe pruebas unitarias para cada command independientemente, luego pruebas de integración para cadenas de command y macros.
Preguntas frecuentes
¿Es este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.
P: ¿Cómo implemento error recovery automation de command? R: Automatiza recuperación de errores usando reintento, fallback y mecanismos de compensación.
P: ¿Deberían los commands soportar múltiples formatos de resultado? R: Sí. Retorna resultados en diferentes formatos (JSON, XML, binario) basado en requisitos del cliente.
P: ¿Cómo implemento sincronización de estado de command? R: Sincroniza estado de command across sistemas distribuidos usando protocolos de consenso.
P: ¿Pueden los commands ejecutarse con aislamiento de recursos? R: Sí. Usa aislamiento de recursos para limitar el impacto de fallos de command.
P: ¿Cómo implemento monitoreo de rendimiento de command? R: Monitorea métricas de ejecución de command para optimización de rendimiento.
P: ¿Deberían los commands soportar múltiples prioridades de ejecución? R: Sí. Implementa colas de prioridad para ejecución crítica de command.
P: ¿Cómo implemento resolución de conflictos de estado de command? R: Resuelve conflictos de ejecución concurrente de command usando estrategias apropiadas.
P: ¿Pueden los commands ejecutarse con locking distribuido? R: Sí. Usa locks distribuidos para prevenir ejecución conflictiva de command.
P: ¿Cómo implemento load shedding de command? R: Rechaza commands cuando el sistema está sobrecargado para prevenir fallos.
P: ¿Deberían los commands soportar múltiples timeouts de ejecución? R: Sí. Implementa diferentes timeouts basados en tipo de command.
P: ¿Cómo implemento versioning de estado de command? R: Versiona estado de command para soportar evolución de schema.
P: ¿Pueden los commands ejecutarse con propagación de contexto? R: Sí. Propaga contexto across ejecución de command para observabilidad.
P: ¿Cómo implemento cuotas de recursos de command? R: Implementa cuotas de recursos para prevenir agotamiento de recursos.
P: ¿Deberían los commands soportar múltiples modos de ejecución? R: Sí. Soporta diferentes modos basados en requisitos.
P: ¿Cómo implemento backup de estado de command? R: Backup estado de command para recuperación de fallos.
P: ¿Pueden los commands ejecutarse con reintento condicional? R: Sí. Implementa reintento condicional basado en tipo de error.
P: ¿Cómo implemento políticas de seguridad de command? R: Aplica políticas de seguridad para ejecución de command.
P: ¿Deberían los commands soportar múltiples estrategias de validación de resultados? R: Sí. Valida resultados contra schemas y reglas de negocio.
P: ¿Cómo implemento profiling de rendimiento de command? R: Perfila ejecución de command para identificar cuellos de botella.
P: ¿Pueden los commands ejecutarse con transacciones distribuidas? R: Sí. Usa protocolos de transacción distribuida para atomicidad.
P: ¿Cómo implemento notificación de error de command? R: Envía notificaciones para fallos de command.
P: ¿Deberían los commands soportar múltiples estrategias de ejecución? R: Sí. Implementa diferentes estrategias basadas en tipo de command.
P: ¿Cómo implemento reconciliación de estado de command? R: Reconcilia estado con sistemas externos para consistencia.
P: ¿Pueden los commands ejecutarse con reserva de recursos? R: Sí. Reserva recursos antes de ejecución de command.
P: ¿Cómo implemento baselines de rendimiento de command? R: Establece baselines y alerta sobre desviaciones.
P: ¿Deberían los commands soportar múltiples estrategias de transformación de resultados? R: Sí. Transforma resultados basado en requisitos del consumidor.
P: ¿Cómo implemento clasificación de errores de command? R: Clasifica errores para determinar estrategias de manejo.
P: ¿Pueden los commands ejecutarse con caché distribuido? R: Sí. Usa cachés distribuidos para compartir resultados.
P: ¿Cómo implemento migración de estado de command? R: Migra estado cuando los schemas cambian.
P: ¿Deberían los commands soportar múltiples contextos de ejecución? R: Sí. Ejecuta en diferentes contextos con permisos apropiados.
P: ¿Cómo implemento optimización de rendimiento de command? R: Optimiza a través de caché, procesamiento por lotes y ejecución paralela.
P: ¿Pueden los commands ejecutarse con shutdown graceful? R: Sí. Implementa shutdown graceful para commands en progreso.
Recursos Relacionados
Patrón Observer
Define un mecanismo de suscripción para notificar a múltiples objetos sobre eventos. Patrón de diseño conductual para comunicación basada en eventos.
PatternPatrón Strategy
Define una familia de algoritmos, encapsula cada uno y los hace intercambiables. Patrón de diseño conductual para selección flexible de comportamiento.
RecipePruebas Unitarias
Cómo escribir pruebas unitarias rápidas y deterministas con mocks y assertions en Python, JavaScript y Java.
PatternPatrón Interpreter
Define una representación para la gramática de un lenguaje junto con un intérprete que usa la representación para interpretar oraciones. Un patrón de diseño de comportamiento para mini-lenguajes.
PatternPatrón Memento
Captura y restaura el estado interno de un objeto sin violar el encapsulamiento. Un patrón de comportamiento para deshacer/rehacer.
PatternPatrón Template Method
Define el esqueleto de un algoritmo en una clase base, permitiendo que las subclases sobreescriban pasos específicos sin cambiar la estructura del algoritmo. Un patrón de diseño de comportamiento.