Command Design Pattern in Rust

1. Definition

The Command design pattern encapsulates a request as an object, thereby allowing users to parameterize clients with different requests, queue requests, and support operations like undo and redo. It decouples the object that invokes the command from the object that knows how to perform the command.

2. Problem Statement

Consider a scenario where you need to parameterize objects with operations, provide support for undo/redo functionality, or need an abstraction for operations that may be delayed, queued, or executed remotely. How can you achieve this without tightly coupling the invoker and receiver of the request?

3. Solution

Wrap the request as a command object. This object has a method to execute the request. The invoker invokes this method, but it's the command object that knows the actual receiver and the action to be performed.

4. Real-World Use Cases

1. GUI buttons and menu items in software applications.

2. Remote procedure invocation systems.

3. Macro recording and playback in software applications.

5. Implementation Steps

1. Define a command interface with an execute method.

2. Create one or more concrete classes that implement this command interface, encapsulating the command logic.

3. Define an invoker class to keep a reference to these command objects and call their execute methods.

6. Implementation in Rust Programming

// Command trait representing the command interface
trait Command {
    fn execute(&self);
}
// Concrete Command
struct LightOnCommand {
    light: Light,
}
impl LightOnCommand {
    pub fn new(light: Light) -> Self {
        LightOnCommand { light }
    }
}
impl Command for LightOnCommand {
    fn execute(&self) {
        self.light.turn_on();
    }
}
struct Light {
    is_on: bool,
}
impl Light {
    pub fn new() -> Self {
        Light { is_on: false }
    }
    pub fn turn_on(&mut self) {
        self.is_on = true;
        println!("Light is ON");
    }
}
// Invoker
struct RemoteControl {
    command: Box<dyn Command>,
}
impl RemoteControl {
    pub fn new(command: Box<dyn Command>) -> Self {
        RemoteControl { command }
    }
    pub fn press_button(&self) {
        self.command.execute();
    }
}
// Client code
fn main() {
    let light = Light::new();
    let light_on = LightOnCommand::new(light);
    let remote = RemoteControl::new(Box::new(light_on));
    remote.press_button();
}

Output:

"Light is ON"

Explanation:

1. The Command trait provides a generic interface for executing operations.

2. LightOnCommand is a concrete implementation of the command which encapsulates an action (turning on the light) on a receiver (Light object).

3. The RemoteControl is the invoker. It's given a command to execute when its button is pressed.

4. In the client code, we tie everything together: create a light, wrap the "turn on" operation in a command, and associate this command with a remote control.

7. When to use?

Use the Command pattern when:

1. You want to parameterize objects with operations to be executed at a later time.

2. You need to queue requests, schedule their execution, or execute them remotely.

3. You want to support undoable operations.

4. You want to decouple classes that invoke operations from classes that perform these operations.


Comments