Memento Design Pattern in Rust

1. Definition

The Memento Design Pattern captures an object's internal state so that the object can be returned to that state later. It promotes the ability to restore an object to its previous state, which can be useful for features like "undo" in software applications.

2. Problem Statement

In many applications, users expect the ability to revert actions. Implementing "undo" functionality directly within the main object can make the object's implementation complex and hard to maintain.

3. Solution

The solution is to use a separate object (the Memento) to store the state of the main object. The main object can then create and return these Memento objects as needed, without revealing its internal structure. A separate caretaker can manage these mementos without needing to know the details of the main object's state.

4. Real-World Use Cases

1. Text editors that offer "undo" functionality.

2. Game systems that allow players to save progress and load it later.

3. Database systems that provide transactional capabilities, allowing rollback to previous states.

5. Implementation Steps

1. Define the Memento class that will store the internal state of the Originator class.

2. The Originator class creates a memento containing a snapshot of its current internal state.

3. Use the Caretaker class to store these mementos.

4. To undo an action, the Originator uses a memento stored by the Caretaker.

6. Implementation in Rust Programming

// Memento containing the state
struct Memento {
    state: String,
}
impl Memento {
    fn new(state: &str) -> Self {
        Memento {
            state: state.to_string(),
        }
    }
    fn get_state(&self) -> &String {
        &self.state
    }
}
// Originator that produces mementos and can restore its state from them
struct Originator {
    state: String,
}
impl Originator {
    fn set_state(&mut self, state: &str) {
        self.state = state.to_string();
    }
    fn save_to_memento(&self) -> Memento {
        Memento::new(&self.state)
    }
    fn restore_from_memento(&mut self, memento: &Memento) {
        self.state = memento.get_state().clone();
    }
    fn show_state(&self) {
        println!("State = {}", self.state);
    }
}
// Caretaker that keeps track of multiple mementos
struct Caretaker {
    saved_states: Vec<Memento>,
}
impl Caretaker {
    fn new() -> Self {
        Caretaker {
            saved_states: Vec::new(),
        }
    }
    fn add_memento(&mut self, memento: Memento) {
        self.saved_states.push(memento);
    }
    fn get_memento(&self, index: usize) -> &Memento {
        &self.saved_states[index]
    }
}
// Client Code
fn main() {
    let mut originator = Originator {
        state: "Initial State".to_string(),
    };
    let mut caretaker = Caretaker::new();
    originator.show_state();
    caretaker.add_memento(originator.save_to_memento());
    originator.set_state("State #1");
    originator.show_state();
    caretaker.add_memento(originator.save_to_memento());
    originator.set_state("State #2");
    originator.show_state();
    originator.restore_from_memento(caretaker.get_memento(0));
    originator.show_state();
}

Output:

State = Initial State
State = State #1
State = State #2
State = Initial State

Explanation:

1. The Memento class simply holds the state for the Originator.

2. The Originator can produce mementos with its current state and can also restore its state from a given memento.

3. The Caretaker is responsible for keeping track of these mementos.

4. In the client code, different states of the Originator are shown, and the ability to revert to a previous state using a memento is demonstrated.

Using the Memento pattern, the internal state of the Originator remains encapsulated, and the Caretaker doesn't need to know about the structure of this state.

7. When to use?

Use the Memento pattern when:

1. You need to provide a "snapshot" mechanism for objects, enabling the ability to roll back to previous states.

2. Direct access to the internal representation of an object would violate its encapsulation and you need an external object to handle the state without violating this encapsulation.


Comments