In this lesson, you will learn how to encapsulate actions as objects to enable undoable transactions using the Command Pattern.
The Command Pattern Concept
In a transaction system, you often want to keep track of a sequence of operations so that you can reverse them if a failure occurs. Instead of executing code directly, the Command Pattern packages an operation—along with its necessary data—into a structure that implements a common trait.
By defining an interface like Command, you decouple the request for an action from the execution of that action. This allows you to push these objects into a Vec (a history stack) and pop them off one by one to trigger an undo() method.
Implementation with Traits
To build this, we define a trait that forces every operation to define its logic for both doing and undoing the work.
trait Command {
fn execute(&mut self);
fn undo(&mut self);
}
struct AddToBalance {
amount: i32,
account: *mut i32, // Simplified for illustration
}
impl Command for AddToBalance {
fn execute(&mut self) {
unsafe { *self.account += self.amount; }
}
fn undo(&mut self) {
unsafe { *self.account -= self.amount; }
}
}
Managing Transactions
Because all commands implement the same trait, you can store a heterogeneous list of different actions in a single collection. Using composition via trait objects, your transaction manager doesn't need to know if it is dealing with a balance adjustment, a user rename, or a database record change; it only needs to know how to call .execute() or .undo() on the items in its stack.
When an error occurs during a batch of operations, you simply iterate backward through your history stack and invoke .undo() on each command. This turns complex, multi-step state changes into atomic, reversible transactions without polluting your business logic with manual rollback code.
Key Takeaway: By encapsulating actions within a trait-based Command interface, you can treat operations as data, enabling robust undo-redo functionality and reliable transaction rollback in complex state systems.