프로그래밍/Design Pattern

메멘토 패턴 (Memento Pattern): Rust로 구현

Lou Park 2023. 7. 30. 13:15

메멘토는 세부 구현을 드러내지 않으면서 객체의 이전 상태를 복원하고, 저장할 수 있게 해주는 디자인 패턴이다.

문제

간단한 텍스트 편집기를 개발한다고 가정해보자.

상태 스냅샷을 만드는것을 간단히 생각해보면, 저장소에 모든 필드들을 저장소에 저장하는 것을 생각할 수 있다. 하지만 아래와 같은 문제들이 있을 수 있다.

  • 캡슐화로 인해 모든 필드들에 접근할 수 없을지도 모른다.
  • 나중에 텍스트 편집기 클래스를 리팩토링하거나 일부 필드를 추가/제거 한다고 했을때 상태 복사를 담당하는 클래스도 같이 변경해야하는 번거로움이 생긴다.
  • 상태를 저장 및 복원하기 위해 스냅샷도 필드들을 모두 열어놓아야(public)한다.

솔루션

메멘토 패턴은 앞에서 생각했던 것 처럼 다른 객체가 외부에서 에디터의 상태를 복사하려고 시도하는 대신, 상태 스냅샷 생성을 실제 상태를 소유하고있는 에디터 객체에 위임한다.

이 패턴은 상태의 복사본을 memento라는 특수한 객체에 저장하도록한다. memento는 memento를 생성한 객체를 제외한 다른 객체에서는 액세스 할 수 없다.

외부에서는 스냅샷의 메타 데이터(생성 시간, 작업 이름 등)를 가져올 수 있지만, 상태는 가져올 수 없는 제한된 인터페이스를 사용해서만 memento와 통신할 수 있다.

 

 

이렇게 하면 일반적으로 Caretaker라고하는 다른 객체에 내에 스냅샷의 히스토리를 저장할 수 있다. 또 생성자인 에디터 클래스는 memento 내부의 모든 필드에 접근가능하므로 마음대로 이전 상태를 복원할 수 있다.

구조

중첩 클래스 기반 구현

 

 

  • Originator 클래스는 자체 상태 스냅샷을 생성하고, 필요할때 복원할 수 있다.
  • Memento는 상태 스냅샷 역할을 하는 객체다. Memento를 Immutable로 만들고 생성자를 통해 한 번만 값을 전달하는 것이 일반적이다.
  • Caretaker는 상태 스냅샷을 언제 만들지, 왜 만들지, 상태복원을 언제해야하는지를 알고 있다. 과거의 상태를 복원하려할때 Caretaker는 스택에서 맨 위의 Memento를 가져와서 Originator의 복원 메소드에 전달한다.
  • 이 구현에서 Memento 클래스는 Originator 내부에 위치한다. 이렇게 하면 생성자가 비공개로 선언된 경우에도 Memento의 필드와 메소드에 액세스할 수 있다. 반면 Caretaker는 Memento의 필드 및 메소드에 제한적으로 액세스할 수 있으므로, 스택에 Memento를 저장할 수는 있지만 상태를 조작하지는 못한다.

엄격한 캡슐화로 구현

 

 

  1. 이렇게 구현하면 여러 타입의 Originator와 Memento를 가질 수 있다. 각 Originator는 해당 Memento 클래스와 연결된다.
  2. 상태 복구 방법이 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);
}

 

자료 출처 https://refactoring.guru/ko