Command
Un patrón de comportamiento que encapsula una solicitud como un objeto, para poder guardarla, encolarla o deshacerla.
Command es un patrón de comportamiento que convierte una solicitud en un objeto independiente, que se puede parametrizar, guardar en una cola, loguear o deshacer.
El problema
El panel de administración de AndesShop necesita poder deshacer ciertas acciones: aplicar un descuento a un pedido, cancelar un pedido, reponer stock manualmente. Si cada botón de la interfaz llama directamente al método correspondiente (order.applyDiscount(10), order.cancel()), no hay ningún lugar donde registrar qué se hizo ni cómo revertirlo — el “deshacer” tendría que reconstruirse a mano para cada acción distinta.
La solución
Command envuelve cada acción en un objeto propio, con un método execute() que la ejecuta y, opcionalmente, un método undo() que la revierte. La interfaz que dispara las acciones (los botones del panel) deja de llamar métodos de negocio directamente y pasa a ejecutar objetos Command — que se pueden guardar en un historial para deshacer, encolar para ejecutar después, o loguear para auditoría.
classDiagram
class Command {
<<interface>>
+execute() void
+undo() void
}
class ApplyDiscountCommand {
-order Order
-discount Money
-previousTotal Money
+execute() void
+undo() void
}
class CancelOrderCommand {
-order Order
-previousStatus OrderStatus
+execute() void
+undo() void
}
class CommandHistory {
<<invoker>>
-history Deque~Command~
+run(Command) void
+undoLast() void
}
class Order {
<<receiver>>
}
Command <|.. ApplyDiscountCommand
Command <|.. CancelOrderCommand
CommandHistory o-- "0..*" Command : history
ApplyDiscountCommand --> "1" Order : receiver
CancelOrderCommand --> "1" Order : receiverEjemplo en Java
interface Command {
void execute();
void undo();
}
class ApplyDiscountCommand implements Command {
private final Order order;
private final double discountPercent;
private double previousPrice;
public ApplyDiscountCommand(Order order, double discountPercent) {
this.order = order;
this.discountPercent = discountPercent;
}
public void execute() {
previousPrice = order.getPrice();
order.applyDiscount(discountPercent);
}
public void undo() {
order.setPrice(previousPrice);
}
}
// El invocador: no sabe qué hace cada Command, solo sabe ejecutarlo y guardarlo
class CommandHistory {
private final Deque<Command> history = new ArrayDeque<>();
public void run(Command command) {
command.execute();
history.push(command);
}
public void undoLast() {
if (!history.isEmpty()) {
history.pop().undo();
}
}
}
// Client code
CommandHistory history = new CommandHistory();
history.run(new ApplyDiscountCommand(order, 10));
// ...
history.undoLast(); // revierte el descuento, sin que CommandHistory sepa qué era
Cuándo usarlo
- Cuando necesitás poder deshacer o rehacer acciones.
- Cuando querés encolar, programar o loguear solicitudes para ejecutarlas más tarde o en otro proceso.
- Cuando querés parametrizar un objeto con una acción a ejecutar, en vez de con los datos crudos de esa acción.
Cuándo evitarlo
Si las acciones son simples y no necesitás deshacerlas, encolarlas ni loguearlas, envolverlas en objetos Command agrega una capa que no se aprovecha.
Ventajas y desventajas
| Ventajas | Desventajas |
|---|---|
| Desacopla quién dispara una acción de quién sabe cómo ejecutarla | Agrega una clase nueva por cada tipo de acción distinta |
| Permite implementar deshacer/rehacer de forma natural | Puede resultar excesivo para acciones simples que nunca necesitan revertirse |
| Las acciones se pueden serializar, encolar o loguear como cualquier otro objeto |
Relación con otros patrones
- Memento se usa a menudo junto con Command para poder implementar el
undo()guardando una foto del estado anterior, en vez de revertir campo por campo. - Chain of Responsibility y Command se combinan cuando una solicitud viaja por una cadena de manejadores, encapsulada como Command.
