메멘토 패턴

메멘토 패턴(memento pattern)은 객체를 이전 상태로 되돌릴 수 있는 기능을 제공하는 소프트웨어 디자인 패턴이다. (롤백을 통한 실행 취소)

메멘토 패턴은 3개의 객체로 구현된다: 오리지네이터(originator), 케어테이커(caretaker), 메멘토(memento). 오리지네이터는 내부 상태를 보유하고 있는 일부 객체이다. 케어테이커는 오리지네이터에 대해 무언가를 하지만 변경에 대한 실행 취소를 하기를 원한다.  케어테이커는 먼저 오리지네이터에게 메멘토 객체를 요청한다. 그 뒤 예정된 일련의 명령을 수행한다. 명령 이전의 상태로 되돌리기 위해 메멘토 객체를 오리지네이터에 반환한다. 메멘토 객체 자신은 불투명 자료형(케어테이커는 변경할 수 없거나 변경해서는 안 되는)이다.

메멘토 패턴의 전형적인 예로 유사난수의 시드(seed), 유한 상태 기계의 상태(state)를 포함한다.

개요 
메멘토 디자인 패턴은 유연하고 재사용 가능한 객체 지향 소프트웨어를 설계하기 위해 반복되는 디자인 문제를 해결하는 방법, 즉 객체는 구현, 변경, 테스트, 재사용이 쉬워야 한다는 것을 기술하는, 잘 알려진 23가지 GoF 디자인 패턴들 중 하나이다. 메멘토 패턴은  초기 HP 제품을 위해 Noah Thompson과 Dr.Drew Clinkenbeard에 의해 제작되었다.

메멘토 디자인 패턴이 해결할 수 있는 문제는 무엇인가?

 객체의 내부 상태가 외부적으로 저장되어야 이후에 객체가 그 상태를 복구할 수 있다.
 객체의 캡슐화가 훼손되지 않아야 한다.

문제는 잘 디자인된 객체는 캡슐화되어 있기 때문에 그 표현(데이터 구조)이 내부에 숨겨져 있어 객체의 외부에서 접근할 수 없다는 것이다.

메멘토 디자인 패턴이 설명하는 해결책은 무엇인가?

객체(오리지네이터) 자체가 다음 책임을 갖도록 한다.

 (메멘토) 객체에 그 내부 상태를 저장
 (메멘토) 객체로부터 이전 상태를 복구

메멘토를 생성한 오리지네이터만이 접근이 허용된다.

클라이언트(케어테이커)는 오리지네이터로부터 메멘토를 요청하고(오리지네이터의 내부 상태를 저장하기 위해) 오리지네이터에게 메멘토를 다시 돌려줄 수 있다(이전 상태로 복구하기 위해).

이는 캡슐화를 훼손하지 않고 오리지네이터의 내부 상태를 저장하고 복구할 수 있게해준다.

아래 UML 클래스 및 시퀀스 다이어그램을 함께 보자.

구조

UML 클래스 및 시퀀스 다이어그램 
위 UML 클래스 다이어그램에서, Caretaker 클래스는 오리지네이터의 내부 상태를 저장(createMemento())하고 복구(restore(memento))하기 위해 Originator 클래스를 참조한다.

Originator 클래스는 다음을 구현한다.

(1) 오리지네이터의 현재 내부 상태를 저장하는 Memento 객체를 생성하고 돌려주는 createMemento()

(2) Memento 객체로 전달된 상태를 복구하는 restore(memento)

UML 시퀀스 다이어그램은 런타임 인터렉션을 보여준다.

(1) 오리지네이터의 내부 상태를 저장: Caretaker 객체는, Memento 객체를 생성하고 현재 내부 상태를 저장하고(setState()), Caretaker로 Memento를 돌려주는 Originator 객체의 createMemento()를 호출한다.

(2) 오리지네이터의 내부 상태를 복구: Caretaker는 Originator 객체의 restore(memento)를 호출하고 복구할 상태를 저장하고있는 Memento 객체를 가져온다(getState()).

자바 예시 
다음 자바 프로그램은 메멘토 패턴의 "undo" 사용을 보여준다.
 import java.util.List;
 import java.util.ArrayList;
 class Originator {
     private String state;
     // The class could also contain additional data that is not part of the
     // state saved in the memento..

     public void set(String state) {
         this.state = state;
         System.out.println("Originator: Setting state to " + state);
     }

     public Memento saveToMemento() {
         System.out.println("Originator: Saving to Memento.");
         return new Memento(this.state);
     }

     public void restoreFromMemento(Memento memento) {
         this.state = memento.getSavedState();
         System.out.println("Originator: State after restoring from Memento: " + state);
     }

     public static class Memento {
         private final String state;

         public Memento(String stateToSave) {
             state = stateToSave;
         }

         // accessible by outer class only
         private String getSavedState() {
             return state;
         }
     }
 }

 class Caretaker {
     public static void main(String[] args) {
         List<Originator.Memento> savedStates = new ArrayList<Originator.Memento>();

         Originator originator = new Originator();
         originator.set("State1");
         originator.set("State2");
         savedStates.add(originator.saveToMemento());
         originator.set("State3");
         // We can request multiple mementos, and choose which one to roll back to.
         savedStates.add(originator.saveToMemento());
         originator.set("State4");

         originator.restoreFromMemento(savedStates.get(1));
     }
 }
출력은 다음과 같다.
 Originator: Setting state to State1
 Originator: Setting state to State2
 Originator: Saving to Memento.
 Originator: Setting state to State3
 Originator: Saving to Memento.
 Originator: Setting state to State4
 Originator: State after restoring from Memento: State3
This example uses a String as the state, which is an immutable object in Java. In real-life scenarios the state will almost always be an object, in which case a copy of the state must be done.

이 예시에서는 Java에서 불변한 객체인 String을 상태로써 사용한다. 실제 시나리오에서는 상태가 거의 항상 객체일 것이며, 상태의 복사가 반드시 이루어져야 한다.

내부 클래스를 선언한것에 대해 구현이 취약점을 보인다고 말할 수 있을 것이다. 이 메멘토 전략은 오리지네이터가 하나 이상일 때 적용한다면 더 나을 것이다.

메멘토를 달성하는 다른 세 가지 주요 방법은 다음과 같다.

 직렬화.
 동일한 패키지에 선언된 클래스.
 객체에 저장/복구 작업을 할 수 있는 프록시를 통해 접근할 수도 있는 객체.

C# 예시 
메멘토 패턴은 캡슐화를 훼손하지 않고 객체의 내부 상태를 캡쳐할 수 있게 해주므로 이후에 필요한 경우 변경사항을 되돌리거나 취소할 수 있다. 다음 코드에서는 객체에서 발생한 변경사항을 취소하기 위해 메멘토 객체가 실제로 사용되는 것을 볼 수 있다.
  1 public class OriginalObject
  2 {
  3     public string String1 { get; set; }
  4     public string String2 { get; set; }
  5
  6     public OriginalObject(string str1, string str2)
  7     {
  8         this.String1 = str1;
  9         this.String2 = str2;
 10     }
 11
 12     public void SetMemento(Memento memento)
 13     {
 14         this.String1 = memento.string1;
 15         this.String2 = memento.string2;
 16     }
 17
 18     public Memento CreateMemento()
 19     {
 20         return new Memento(this.String1, this.String2);
 21     }
 22 }
 23
 24 //Memento object
 25 public class Memento
 26 {
 27     public readonly string string1;
 28     public readonly string string2;
 29
 30     public Memento(string str1, string str2)
 31     {
 32         this.string1 = str1;
 33         this.string2 = str2;
 34     }
 35 }
 36
 37 //CareTaker Object
 38 public class CareTaker
 39 {
 40     public Memento Memento { get; set; }
 41 }
 42
 43 //Client
 44 class Program
 45 {
 46     static void Main(string[] args)
 47     {
 48         // Create Originator object which container first state of "First" and "One".
 49         // The Memento will remember these value.
 50         OriginalObject original = new OriginalObject("First", "One");
 51
 52         // Create first State and store to caretaker
 53         Memento firstMemento = original.CreateMemento();
 54         CareTaker caretaker = new CareTaker();
 55         caretaker.Memento = firstMemento;
 56
 57         // Change into second state; "Second" and "Two".
 58         original.String1 = "Second";
 59         original.String2 = "Two";
 60
 61         // Retrieve back first State
 62         original.SetMemento(caretaker.Memento);
 63
 64     }
 65 }

Python 예시 
 """
 Memento pattern example.
 """

 class Memento(object):
     def __init__(self, state):
         self._state = state

     def get_saved_state(self):
         return self._state

 class Originator(object):
     _state = ""

     def set(self, state):
         print("Originator: Setting state to", state)
         self._state = state

     def save_to_memento(self):
         print("Originator: Saving to Memento.")
         return Memento(self._state)

     def restore_from_memento(self, memento):
         self._state = memento.get_saved_state()
         print("Originator: State after restoring from Memento:", self._state)

 saved_states = []
 originator = Originator()
 originator.set("State1")
 originator.set("State2")
 saved_states.append(originator.save_to_memento())

 originator.set("State3")
 saved_states.append(originator.save_to_memento())

 originator.set("State4")

 originator.restore_from_memento(saved_states[0])

각주

외부 링크 

 Description of Memento Pattern in Ada
 Memento UML Class Diagram with C# and .NET code samples
 SourceMaking Tutorial
 Memento Design Pattern using Java

소프트웨어 디자인 패턴