메멘토는 세부 구현을 드러내지 않으면서 객체의 이전 상태를 복원하고, 저장할 수 있게 해주는 디자인 패턴이다.
문제
간단한 텍스트 편집기를 개발한다고 가정해보자.
상태 스냅샷을 만드는것을 간단히 생각해보면, 저장소에 모든 필드들을 저장소에 저장하는 것을 생각할 수 있다. 하지만 아래와 같은 문제들이 있을 수 있다.
- 캡슐화로 인해 모든 필드들에 접근할 수 없을지도 모른다.
- 나중에 텍스트 편집기 클래스를 리팩토링하거나 일부 필드를 추가/제거 한다고 했을때 상태 복사를 담당하는 클래스도 같이 변경해야하는 번거로움이 생긴다.
- 상태를 저장 및 복원하기 위해 스냅샷도 필드들을 모두 열어놓아야(public)한다.
솔루션
메멘토 패턴은 앞에서 생각했던 것 처럼 다른 객체가 외부에서 에디터의 상태를 복사하려고 시도하는 대신, 상태 스냅샷 생성을 실제 상태를 소유하고있는 에디터 객체에 위임한다.
이 패턴은 상태의 복사본을 memento라는 특수한 객체에 저장하도록한다. memento는 memento를 생성한 객체를 제외한 다른 객체에서는 액세스 할 수 없다.
외부에서는 스냅샷의 메타 데이터(생성 시간, 작업 이름 등)를 가져올 수 있지만, 상태는 가져올 수 없는 제한된 인터페이스를 사용해서만 memento와 통신할 수 있다.
이렇게 하면 일반적으로 Caretaker라고하는 다른 객체에 내에 스냅샷의 히스토리를 저장할 수 있다. 또 생성자인 에디터 클래스는 memento 내부의 모든 필드에 접근가능하므로 마음대로 이전 상태를 복원할 수 있다.
구조
중첩 클래스 기반 구현
- Originator 클래스는 자체 상태 스냅샷을 생성하고, 필요할때 복원할 수 있다.
- Memento는 상태 스냅샷 역할을 하는 객체다. Memento를 Immutable로 만들고 생성자를 통해 한 번만 값을 전달하는 것이 일반적이다.
- Caretaker는 상태 스냅샷을 언제 만들지, 왜 만들지, 상태복원을 언제해야하는지를 알고 있다. 과거의 상태를 복원하려할때 Caretaker는 스택에서 맨 위의 Memento를 가져와서 Originator의 복원 메소드에 전달한다.
- 이 구현에서 Memento 클래스는 Originator 내부에 위치한다. 이렇게 하면 생성자가 비공개로 선언된 경우에도 Memento의 필드와 메소드에 액세스할 수 있다. 반면 Caretaker는 Memento의 필드 및 메소드에 제한적으로 액세스할 수 있으므로, 스택에 Memento를 저장할 수는 있지만 상태를 조작하지는 못한다.
엄격한 캡슐화로 구현
- 이렇게 구현하면 여러 타입의 Originator와 Memento를 가질 수 있다. 각 Originator는 해당 Memento 클래스와 연결된다.
- 상태 복구 방법이 Memento에 정의되어 있기 때문에 Caretaker는 Originator로부터 독립될 수 있다.
trait Memento<Originator> {
fn restore(self) -> Originator;
}
struct ConcreteOriginator {
state: u32,
}
impl ConcreteOriginator {
fn save(&self) -> Snapshot {
Snapshot {
state: self.state.to_string(),
}
}
}
impl Memento<ConcreteOriginator> for Snapshot {
fn restore(self) -> ConcreteOriginator {
ConcreteOriginator {
state: self.state.parse().unwrap(),
}
}
}
struct Snapshot {
state: String,
}
struct Caretaker {
history: Vec<Snapshot>,
}
fn main() {
let mut editor = ConcreteOriginator { state: 0 };
let mut caretaker = Caretaker {
history: Vec::new(),
};
caretaker.history.push(editor.save());
editor.state = 1;
caretaker.history.push(editor.save());
editor.state = 2;
println!("Editor state 1: {}", editor.state);
editor = caretaker.history.pop().unwrap().restore();
println!("Editor state 2: {}", editor.state);
editor = caretaker.history.pop().unwrap().restore();
println!("Editor state 3: {}", editor.state);
}