URL: https://www.youtube.com/watch?v=ZXAt8AP3DnY 날짜: 2026-10-11 채널: ArjanCodes
📌 핵심 질문 / 이 글이 다루는 핵심 논점
==객체를 이전 상태로 되돌리는 데 각 연산의 역연산을 일일이 작성하는 대신, 변경 전 상태를 불변 snapshot으로 보관하고 객체가 직접 저장·복원하게 만들면 undo를 작고 독립적인 인프라로 만들 수 있다.==
- Memento pattern은 연산(operation)이 아니라 객체의 상태(state)를 저장한다.
dataclass(frozen=True),Protocol, 현대적인 generic syntax를 사용하면 Gang of Four의 고전적인 역할 구조를 파이썬답게 표현할 수 있다.- snapshot은 객체 전체를 무조건 직렬화하는 기능이 아니라, 특정 복원 목적에 필요한 상태 경계를 명시하는 설계다.
- 상태 복사가 비싸거나 외부 부작용이 포함되면 Command pattern, transaction, compensating action 같은 다른 해법이 더 적절할 수 있다.
undo의 본질은 “무슨 연산을 반대로 실행할까?”가 아니라 “이 목적에 필요한 이전 상태를 어떻게 안전하게 보관하고 복원할까?”라는 질문으로 바뀐다. 상태를 소유한 객체가 자신의 snapshot 생성과 복원을 책임지고, history는 snapshot을 저장·꺼내는 일만 맡으면 새로운 mutation이 추가되어도 undo 인프라를 고칠 필요가 없다.
1. 역연산을 직접 구현할 때 커지는 undo 문제
사용자가 수정할 수 있는 객체에 undo를 추가하는 가장 직관적인 방식은 모든 작업의 반대쪽 로직을 별도로 작성하는 것이다. 그러나 작업 종류가 늘어날수록 이전 상태를 복원하는 정보와 코드가 함께 늘어난다.
1.1. 연산마다 반대 로직을 만들어야 하는 구조
-
객체 변경과 undo의 일대일 대응
- 사용자가 이름을 바꾸면
undo rename을 구현해야 한다. - 단계를 추가하면
undo add step을 구현해야 한다. - 단계를 이동하면
undo move step을 구현해야 한다. - 새로운 연산을 추가할 때마다 그 연산의 undo 버전도 함께 작성해야 한다.
- 사용자가 이름을 바꾸면
-
역연산에 필요한 과거 정보
- 단순히 반대 메서드를 호출하는 것만으로는 충분하지 않다.
- 이름을 바꾸기 전의 문자열, 이동하기 전의 위치, 제거하기 전 단계의 내용과 위치처럼 이전 상태를 다시 만들 수 있는 정보도 보관해야 한다.
- 모든 mutation이 reverse logic과 복원용 데이터를 함께 관리하면 구현이 빠르게 번거로워진다.
1.2. 예제 도메인: 자동화 workflow 편집기
-
모델 구성
Step은 이름(name)과 활성화 여부(enabled)를 가진다.Workflow는 이름과 여러 단계를 가진다.Workflow에는 단계를 몇 개 보유하는지 확인하는 정보가 있고, workflow를 설명하는describe같은 편의 메서드도 둘 수 있다.- 이런 모델은 자동화 시스템의 workflow editor 일부로 볼 수 있다.
-
기본 사용 흐름
main함수가 몇 개의 단계가 있는 workflow를 만든다. 예제 실행에서는 처음에 단계 하나를 둔다.- workflow 이름을 바꾼다.
- 두 번째 단계를 추가한다.
- workflow를 출력하면 이름과 단계 구성이 바뀐 결과가 나타난다.
- 실제 editor라면 단계 추가·삭제·설정·순서 변경 등 더 많은 mutation이 들어갈 수 있다.
-
역연산 방식의 부담
- 각 변경마다 대응하는 reverse logic을 구현해야 한다.
- 이전 상태를 재구성할 만큼 충분한 정보를 각 역연산에 전달해야 한다.
- 연산 자체가 중요한 경우에는 이 방식이 유용할 수 있지만, 요구사항이 단지 “객체를 이전 상태로 돌려놓기”라면 더 단순한 선택지가 있다.
1.3. 작업 전 안내와 예제의 전환
-
소규모 대면 행사 구상
- 네덜란드 위트레흐트(Utrecht)에서 다음 해에 대면(in-person) 행사를 열자는 구상이 소개된다.
- 위트레흐트는 Arjan이 거주하는 곳이다.
- 이틀 동안 소수의 개발자가 모여 함께 시간을 보낸다.
- Arjan이 가르치고, 참가자가 어려움을 겪는 구체적인 주제를 충분한 토론 시간과 함께 깊이 다룬다.
- 저녁 식사와 다른 활동도 함께 진행할 계획이다.
- 관심이 있으면
arjancodes.com/retreat의 대기자 명단(waitlist)에 등록할 수 있으며 링크는 설명란에도 제공된다. - 참가자에게는 작고 밀도 높은 개발자 모임이라는 성격이 강조된다.
-
예제로 돌아가기
- undo를 설명하기 위해 workflow editor를 출발점으로 삼는다.
- 이름과 활성화 여부를 가진 step, 이름과 단계 목록을 가진 workflow를 사용한다.
- 먼저 직접적인 역연산의 부담을 확인한 다음 snapshot 방식으로 전환한다.
2. Memento pattern과 snapshot의 핵심 아이디어
Memento pattern은 변경을 반대로 실행하지 않고, 변경 직전의 객체 상태를 snapshot으로 남긴 뒤 필요할 때 그 snapshot을 다시 적용한다.
2.1. 변경 전 상태를 먼저 저장하기
-
snapshot의 순서
- 변경을 실행하기 전에 현재 workflow 상태의 snapshot을 만든다.
- 그 뒤 이름 변경이나 단계 추가 같은 작업을 수행한다.
- undo가 필요하면 저장해 둔 snapshot을 workflow에 복원한다.
-
역연산 제거
- snapshot과 restore는 “어떤 작업을 했는가”를 알 필요가 없다.
- 이후 단계 섞기(shuffle)나 다른 편집 연산이 추가되어도 snapshot·restore 메커니즘은 그 연산의 종류를 신경 쓰지 않는다.
- 복원의 기준은 마지막 연산의 반대가 아니라, 저장된 시점의 상태다.
2.2. 불변 WorkflowSnapshot 만들기
-
snapshot을 표현하는 데이터 클래스
- snapshot을 나타내는
WorkflowSnapshot데이터 클래스를 만든다. - snapshot에는 편집 가능한 workflow 이름을 보관하는
name필드를 둔다. - workflow의 단계도 보관하되, 변경 가능한 list가 아니라 tuple로 저장한다.
- snapshot 자체가 수정되면 복원 지점이 흔들리므로
@dataclass(frozen=True)로 만든다.
- snapshot을 나타내는
-
tuple을 선택하는 이유
- 원본 workflow는 단계를 계속 추가·삭제해야 하므로 list가 자연스럽다.
- snapshot은 과거 시점의 기록이므로 편집 가능하면 안 된다.
- tuple과 frozen dataclass를 함께 사용하면 저장된 상태를 의도치 않게 바꾸는 일을 막을 수 있다.
-
Memento의 의미
- Memento는 특정 시점의 workflow를 표현하는 값이다.
- workflow 내부의 실행 중 연결이나 부수적인 runtime 정보가 아니라, 복원하려는 편집 상태의 표현만 담는다.
2.3. Originator에 snapshot과 restore를 둔다
-
snapshot동작-
상태를 소유하는
Workflow클래스에 snapshot 메서드를 둔다. -
메서드는
WorkflowSnapshot을 반환한다. -
반환값에는 현재 이름과 현재 단계 list를 tuple로 변환한 값이 들어간다.
-
개념적으로 다음과 같은 형태다.
def snapshot(self) -> WorkflowSnapshot: return WorkflowSnapshot( name=self.name, steps=tuple(self.steps), )
-
-
restore동작-
restore는 값을 반환하지 않는다. -
snapshot에서 이름을 읽어 현재 workflow의 이름에 대입한다.
-
snapshot의 tuple인 단계를 list로 바꾸어 현재 workflow의 단계에 대입한다.
-
개념적으로 다음과 같은 형태다.
def restore(self, snapshot: WorkflowSnapshot) -> None: self.name = snapshot.name self.steps = list(snapshot.steps)
-
-
간단한 실행 시나리오
- 원래 workflow에서
snapshot = workflow.snapshot()을 실행한다. - workflow 이름을 바꾸고 두 번째 단계를 추가한다.
- 변경된 workflow를 출력하면 이름과 단계 추가가 반영된 결과를 얻는다.
workflow.restore(snapshot)을 호출한다.- 다시 출력하면 이름과 단계가 원래 구성으로 돌아온다.
- 이름 변경이나 단계 추가의 역연산을 별도로 작성하지 않아도 된다.
- 원래 workflow에서
3. 객체 역할을 분리해 캡슐화와 추상화를 지키기
Memento의 강점은 단순히 과거 값을 저장하는 데서 끝나지 않는다. 상태를 누가 알고, history가 무엇을 알아야 하는지 경계를 깔끔하게 만든다.
3.1. Originator, Memento, Caretaker
-
Originator:
Workflow- 저장하고 복원해야 하는 상태를 소유하는 객체다.
- 자신의 상태에서 snapshot을 만들 수 있어야 한다.
- snapshot을 받아 내부 상태를 복원하는 방법도 자신이 알고 있어야 한다.
-
Memento:
WorkflowSnapshot- 저장된 상태를 담는 객체다.
- 예제에서는 이름과 단계 tuple을 담는다.
- 불변 데이터로 만들어 외부 history나 호출자가 내부 기록을 수정하지 못하게 한다.
-
Caretaker: history
- 나중에 복원할 Memento를 보관한다.
- workflow 내부 구조를 직접 들여다보지 않는다.
- snapshot을 만들 수 있고 복원할 수 있는 객체라는 사실만 알면 된다.
-
Gang of Four와 현대적 해석
- Originator, Memento, Caretaker라는 이름은 Gang of Four의 Design Patterns 책에서 나온다.
- 그 책은 1994년에 출판됐다.
- 2026년의 현대적인 Python 코드에서 당시의 클래스 다이어그램을 문자 그대로 재현할 필요는 없다.
- 이름을 기계적으로 복사하기보다 Python의 타입 시스템과 데이터 클래스로 같은 설계 의도를 표현하면 된다.
3.2. Protocol로 snapshot 가능한 객체를 추상화하기
-
workflow 전용 caretaker에서 벗어나기
- caretaker가
Workflow를 직접 알아야 한다고 가정하면 재사용성이 떨어진다. - caretaker에 필요한 능력은 workflow라는 이름이 아니라 snapshot을 생성하고 복원하는 두 메서드다.
- 이 능력을 Python의
Protocol로 표현한다.
- caretaker가
-
generic snapshot protocol
-
snapshot 타입을 나타내는 type variable
T를 선언한다. -
protocol의
snapshot메서드는T를 반환한다. -
protocol의
restore메서드는T타입 snapshot을 받고 아무것도 반환하지 않는다. -
개념적인 형태는 다음과 같다.
T = TypeVar("T") class Snapshotable(Protocol[T]): def snapshot(self) -> T: ... def restore(self, snapshot: T) -> None: ...
-
-
구조적 타이핑(structural typing)
Workflow가 protocol을 상속할 필요는 없다.- 필요한 메서드와 시그니처를 갖고 있으면 protocol을 만족한다.
- 따라서 history는 workflow의 내부 구현이나 상속 계층이 아니라 snapshot 생성·복원이라는 capability에 의존한다.
3.3. generic History caretaker 구현
-
저장소 구조
History[T]는 snapshot 목록을 보관한다.- 초기화할 때 목록은 비어 있다.
- 여러 snapshot을 순서대로 저장하므로 list가 적합하다.
- 개념적인 필드는
snapshots: list[T]다.
-
save동작save는Snapshotable[T]객체를 받는다.- 객체의
snapshot()을 호출한다. - 반환된 snapshot을 목록에 append한다.
save자체는 값을 반환하지 않는다.
-
undo동작-
undo도 snapshot 가능한 객체를 받는다. -
목록이 비어 있으면 복원할 snapshot이 없으므로 아무것도 하지 않고
False를 반환한다. -
snapshot이 있으면 마지막 snapshot을 꺼내 객체에
restore한다. -
복원을 끝낸 뒤 목록에서 최신 snapshot을 제거한다.
-
성공하면
True를 반환한다. -
개념적인 형태는 다음과 같다.
class History(Generic[T]): def __init__(self) -> None: self.snapshots: list[T] = [] def save(self, obj: Snapshotable[T]) -> None: self.snapshots.append(obj.snapshot()) def undo(self, obj: Snapshotable[T]) -> bool: if not self.snapshots: return False obj.restore(self.snapshots.pop()) return True
-
-
사용 코드의 변화
main에서 snapshot을 직접 만들고 복원하는 대신history = History[WorkflowSnapshot]()를 만든다.- 변경 전에
history.save(workflow)를 호출한다. - workflow를 편집한 뒤
history.undo(workflow)를 호출한다. - history가 snapshot 목록을 관리하므로 애플리케이션 코드는 저장과 undo라는 의도만 표현하면 된다.
- history는 workflow가 어떻게 동작하는지 전혀 모른 채 같은 결과를 만들어낸다.
4. 상태 경계와 redo 설계
snapshot을 도입했다고 해서 객체의 모든 필드를 자동으로 복사해야 하는 것은 아니다. 무엇을 저장할지와 어느 방향으로 되돌릴지를 먼저 결정해야 한다.
4.1. snapshot에 포함할 상태를 선택하기
-
편집 상태와 실행 상태 구분
- workflow가 편집 가능한 설정뿐 아니라
last_run_at,execution_count같은 runtime 정보도 가진다고 가정한다. - 이름 변경을 undo한다고 해서 마지막 실행 시각과 실행 횟수까지 과거로 되돌리는 것은 대체로 바람직하지 않다.
- 따라서 snapshot은 모든 attribute를 자동 serialize하는 기능이어서는 안 된다.
- workflow가 편집 가능한 설정뿐 아니라
-
복원 목적에 맞는 상태
- 무엇을 복원하려는지에 관련된 상태만 snapshot에 넣는다.
- 이 편집기에서는 이름과 단계처럼 사용자가 수정하는 workflow configuration이 대상이다.
- 실행 이력이나 runtime telemetry처럼 편집 undo와 무관한 값은 snapshot 경계 밖에 둔다.
-
명시적인 state boundary
- explicit snapshot은 어떤 상태를 복원하는지 문서화한다.
- “전체 객체 복사”보다 “이 목적에 필요한 복원 경계”가 설계의 핵심이다.
- 이 경계를 분명히 해야 undo가 예상하지 못한 runtime 상태까지 되감지 않는다.
4.2. 모든 객체 상태를 쉽게 복사할 수 없는 경우
-
복사하면 안 되거나 복사할 수 없는 자원
- 실제 객체에는 database connection이 포함될 수 있다.
- lock, service, cache, file handle 같은 실행 자원도 내부에 있을 수 있다.
- 거대한 양의 무관한 상태가 섞여 있을 수도 있다.
-
명시적 snapshot의 이점
- 이런 객체를 무작정 깊은 복사하거나 전부 직렬화하면 실패하거나 의미가 없을 수 있다.
- snapshot을 직접 정의하면 복원에 필요한 값만 골라 저장할 수 있다.
- 결과적으로 snapshot 타입이 상태 경계를 코드 수준에서 드러내는 문서가 된다.
4.3. 두 번째 stack으로 redo 추가하기
-
undo·redo 저장소
- 기존
History에 undo list와 redo list를 둔다. - 평소 변경 전 상태는 undo stack에 저장한다.
- 두 stack이 현재 객체 상태와 함께 움직이도록 관리한다.
- 기존
-
undo 시점
- undo하기 전에 현재 객체 상태를 snapshot으로 만들어 redo stack에 넣는다.
- undo stack에서 이전 snapshot을 꺼내 객체를 복원한다.
- 이전 편집 상태로 돌아간 뒤 redo할 수 있는 현재 상태가 보존된다.
-
redo 시점
- redo stack의 snapshot을 undo stack에 append한다.
- 객체를 redo snapshot으로 복원한다.
- 두 stack을 반대 방향으로 이동시키면 undo 전의 편집 상태로 다시 돌아간다.
-
실행 예시
- history와 workflow를 만든다.
- workflow를 저장한다.
- 이름 변경과 단계 추가를 실행한다.
- 변경된 workflow를 출력한다.
- undo를 실행하면 원래 상태가 출력된다.
- redo를 실행하면 undo 직전의 편집된 상태가 다시 출력된다.
5. Memento와 Command pattern의 선택
undo를 구현하는 패턴은 하나가 아니다. Memento는 상태를 저장하고, Command는 실행한 연산을 저장한다.
5.1. 두 패턴의 핵심 차이
-
저장하는 대상
- Memento는 상태(state)를 저장한다.
- Command는 연산(operation)을 저장한다.
- Memento를 복원할 때는 과거 상태를 그대로 적용한다.
- Command를 되돌릴 때는 각 command가 가진 역연산을 실행한다.
-
Memento가 잘 맞는 경우
- 상태를 복사하는 비용이 비교적 싸다.
- 가능한 mutation의 종류가 많다.
- 각 mutation의 reverse logic을 따로 작성하는 것보다 이전 상태를 복원하는 편이 단순하다.
- workflow editor처럼 다양한 편집 연산이 있지만 undo 목표가 설정 상태 복원인 경우가 대표적이다.
-
Command가 잘 맞는 경우
- snapshot이 매우 크다.
- 개별 변경은 작아서 작은 command만 저장하는 편이 효율적이다.
- 이름 변경이나 단계 제거 같은 연산 자체가 도메인 개념으로 중요하다.
- 수행된 연산을 audit trail의 일부로 남겨야 한다.
5.2. 두 패턴을 함께 쓰는 방식
- command로 각 연산을 기록한다.
- 가끔 Memento snapshot을 checkpoint로 남긴다.
- 긴 연산 history를 매번 처음부터 재생하지 않고 checkpoint부터 복원할 수 있다.
- draft state, simulation, 실패한 multi-step operation 뒤의 local application state 복원에도 checkpoint가 유용하다.
5.3. 외부 부작용에는 snapshot만으로 충분하지 않다
-
복원할 수 없는 side effect
- 객체를 이전 메모리 상태로 되돌려도 이메일을 “전송 취소”할 수는 없다.
- 이미 실행한 API call을 snapshot restore만으로 역전할 수 없다.
- 누군가의 bank account로 보낸 돈을 객체 복원만으로 되돌릴 수 없다.
-
외부 시스템이 개입할 때 필요한 것
- transaction을 사용해야 할 수 있다.
- 반대 효과를 내는 compensating action을 설계해야 할 수 있다.
- 분산 시스템에 맞는 별도의 architectural mechanism이 필요할 수 있다.
- Memento는 메모리 안 객체의 상태를 되돌리는 패턴이지 외부 세계의 부작용을 지우는 마법이 아니다.
6. 과도한 적용을 피하는 실무 판단
6.1. 작은 객체에는 단순 복사가 더 낫다
- 객체가 아주 작고 안전하게 복사할 수 있다면 Memento에 맞춘 구조를 새로 만들 필요가 없다.
- 단순히 값을 복사해 두는 방식만으로 요구사항을 충족할 수 있다.
Originator,Memento,Caretaker라는 이름이 고전 책에 나온다는 이유만으로 base class 세 개를 만들면 안 된다.
6.2. snapshot 수와 크기에 따른 메모리 문제
- 상태가 엄청나게 큰 객체라면 snapshot 하나도 큰 메모리를 차지한다.
- snapshot을 많이 쌓으면 memory issue가 생길 수 있다.
- 이 경우 command log, 차분 저장, checkpoint 간격 조정 등 비용을 고려한 전략이 필요하다.
6.3. Python에서 필요한 최소 구성
- 불변 frozen data class 몇 개로 snapshot을 표현한다.
Protocol로 snapshot 생성·복원 능력을 추상화한다.- 작은 generic history class로 stack을 관리한다.
- 고전적인 역할 이름과 상속 구조를 그대로 복제하지 않고도 Memento의 설계 의도를 얻을 수 있다.
7. 설계의 핵심 교훈
7.1. 상태 소유자가 상태를 관리하게 하라
- 상태를 가진 객체가 snapshot을 캡처하는 책임을 진다.
- 같은 객체가 snapshot을 복원하는 책임도 진다.
- 외부 history는 객체 내부의 필드, 복사 규칙, 복원 순서를 알 필요가 없다.
7.2. 인프라는 capability에만 의존하게 하라
- history는 “이것이 workflow인가?”를 묻지 않는다.
- “snapshot을 만들고 복원할 수 있는가?”만 요구한다.
- 이 추상화 덕분에 다른 도메인 객체도 같은 history 인프라에 연결할 수 있다.
- Python의 data class, protocol, generic syntax가 이 경계를 짧고 명확한 코드로 표현한다.
7.3. undo를 설계할 때 점검할 질문
- 되돌리려는 것은 연산 자체인가, 특정 시점의 상태인가?
- snapshot에 포함해야 할 편집 상태와 제외해야 할 runtime 상태는 무엇인가?
- 상태를 복사하는 비용과 snapshot 수에 따른 메모리 비용은 감당할 수 있는가?
- 외부 side effect를 실제로 보상할 transaction 또는 compensating action이 필요한가?
- 변경 연산이 도메인 개념이거나 audit trail이어야 한다면 Command가 더 적합한가?
주요 발언 모음
“Memento는 상태를 저장하고, Command는 연산을 저장한다.”
“상태를 소유한 객체가 상태를 캡처하고 복원하는 책임을 갖게 하라.”
“snapshot은 모든 attribute를 자동으로 serialize한다는 뜻이 아니다. 복원하려는 대상에 관련된 상태를 캡처해야 한다.”
“객체를 복원한다고 해서 side effect가 undo되는 것은 아니다.”
“고전적인 design pattern을 그대로 구현하지 말고, 언어에 어떤 흥미로운 기능이 있는지 살펴 그 패턴을 더 효과적으로 표현하라.”
핵심 데이터 & 수치
- 1994년: Gang of Four의 Design Patterns가 출판된 시점이다.
- 2026년: 현대 Python에서는 책의 클래스 다이어그램을 문자 그대로 재현할 필요가 없다는 맥락의 기준 연도다.
- 세 가지 역할: Memento pattern의 전통적인 역할은 Originator, Memento, Caretaker다.
- 두 가지 stack: redo를 추가할 때 undo stack과 redo stack을 함께 유지한다.
- 두 가지 저장 대상: Memento는 state를, Command는 operation을 저장한다.
- 예제 초기 상태: workflow에는 처음에 step 하나가 있고, 이름 변경 뒤 두 번째 step을 추가한다.
결론 및 시사점
- 객체를 이전 상태로 돌리는 요구사항이라면 각 mutation의 reverse logic부터 만들지 말고 snapshot 가능성을 검토한다.
- 변경 전에 snapshot을 저장하고, undo 때 최신 snapshot을 복원하는 흐름으로 history를 단순화한다.
- snapshot은
frozen dataclass와 tuple로 불변성을 확보한다. Protocol[T]로 snapshot과 restore capability만 추상화하면 caretaker가 도메인 객체의 내부 구현에서 분리된다.History[T]는 snapshot을 append하고 pop하는 generic 인프라로 남겨 새로운 mutation에도 영향을 받지 않게 한다.- snapshot에는 복원 목적에 필요한 편집 상태만 넣고 runtime 정보와 외부 자원은 경계 밖에 둔다.
- redo는 undo와 반대 방향으로 snapshot을 옮기는 두 번째 stack으로 확장할 수 있다.
- 상태 복사가 싸고 mutation 종류가 많으면 Memento를, 상태가 크거나 연산 자체가 중요한 경우 Command를 선택한다.
- command history와 checkpoint snapshot을 결합하면 긴 기록을 효율적으로 복원할 수 있다.
- 이메일 전송, API 호출, 송금처럼 외부에 남는 side effect는 객체 restore만으로 취소되지 않는다.
- 외부 시스템이 개입하면 transaction이나 compensating action 같은 별도 설계가 필요하다.
- 작은 객체는 pattern 구조보다 단순 복사가 더 적절할 수 있다.
- 큰 상태와 많은 snapshot은 memory issue를 만들 수 있으므로 저장 비용을 평가해야 한다.
- 고전적인 클래스 이름과 상속을 기계적으로 추가하는 것은 Python다운 설계가 아니다.
- 상태를 소유한 객체가 캡처와 복원을 책임지는 것이 캡슐화의 중심이다.
핵심 20줄 요약
사용자 편집 객체의 undo는 모든 mutation에 역연산을 붙이는 방식으로 시작하기 쉽다. 이름 변경과 단계 추가만 있어도 각 역연산에는 과거 값을 재구성할 정보가 필요하다. Memento pattern은 연산을 반대로 실행하지 않고 변경 전 상태를 snapshot으로 저장한다. snapshot은 변경 전에 만들고, undo 시 저장된 시점의 객체 상태를 복원한다. workflow 예제는 이름과 단계 목록을 편집 가능한 상태로 가진다. WorkflowSnapshot은 이름과 단계 tuple을 담는 불변 frozen data class다. 원본 workflow의 list는 snapshot에서 tuple로 바뀌어 과거 기록의 수정을 막는다. Workflow가 snapshot 생성과 restore를 직접 책임지면 자신의 상태 경계를 정확히 알 수 있다. Originator는 상태를 소유한 workflow고 Memento는 저장된 snapshot이다. Caretaker인 history는 snapshot을 보관하지만 workflow 내부 구조를 알지 못한다. Python Protocol은 snapshot과 restore capability를 상속 없이 표현한다. generic History는 snapshot 목록에 저장하고 최신 기록을 꺼내 복원한다. undo할 기록이 없으면 아무것도 바꾸지 않고 실패를 나타내는 False를 반환한다. redo는 현재 상태를 redo stack에 저장하고 두 stack 사이에서 snapshot을 이동시킨다. runtime 실행 횟수나 마지막 실행 시각은 편집 undo용 snapshot에서 제외할 수 있다. database connection과 file handle 같은 실행 자원은 전체 객체 복사의 대상이 아니다. 상태 복사가 싸고 mutation이 많으면 Memento가 역연산보다 단순하다. 상태가 크거나 연산 자체가 audit trail이면 Command pattern이 더 적합하다. 외부 이메일과 API 호출의 side effect는 객체 restore만으로 되돌릴 수 없다. 현대 Python에서는 frozen data class와 Protocol만으로 고전 패턴의 의도를 작고 깨끗하게 구현할 수 있다.
