274. 메멘토 (Memento) - 객체 상태 저장 및 복원
핵심 인사이트 (3줄 요약)
- 본질: 메멘토(Memento) 패턴은 객체의 내부 상태를 캡슐화가 깨지지 않는 선에서 외부로 추출하여 별도의 객체(메멘토)로 저장해 두고, 나중에 객체를 그 상태로 다시 복원(Restore)할 수 있게 해주는 행동(Behavioral) 패턴이다.
- 가치: 객체의 내부 정보를 외부(Caretaker)에서 안전하게 보관하면서도, 외부 객체가 그 내부 구조를 들여다보거나 조작하지 못하도록 '접근 권한(은닉성)'을 완벽하게 통제하여 정보 은닉(Information Hiding)을 보장한다.
- 융합: 커맨드(Command) 패턴의 실행 취소(Undo/Redo) 메커니즘을 완성하기 위한 핵심 부품으로 사용되며, 게임의 세이브/로드 시스템, 텍스트 에디터의 임시 저장, 데이터베이스 트랜잭션 롤백(Rollback) 아키텍처의 근간을 이룬다.
Ⅰ. 개요 및 필요성 (Context & Necessity)
-
개념: 메멘토 패턴은 현재 상태를 저장할 원본 객체(
Originator), 상태를 캡슐화한 저장소(Memento), 그리고 이 저장소를 보관만 하고 절대로 열어보지 않는 보관자(Caretaker) 세 객체의 협력으로 이루어지는 상태 백업 패턴이다. -
필요성: RPG 게임 캐릭터의 현재 상태(HP, MP, X/Y 좌표, 인벤토리)를 세이브(저장)해야 한다. 만약 메인 게임 루프(Caretaker)가
Character.getHP(),getInventory()등 수많은 Getter를 호출해 직접 변수에 저장해 둔다면, 나중에 캐릭터 클래스 내부 구조(예: MP가 분노 게이지로 바뀜)가 바뀔 때 메인 게임 루프의 저장 로직도 싹 다 고쳐야 한다(강한 결합). 객체의 상태를 백업하려면 캡슐화(Encapsulation)를 깨지 않고 안전하게 덩어리째 넘겨주는 방식이 필요했다. -
💡 비유: 비밀 금고(Originator)의 물건을 이사 갈 때 보관업체(Caretaker)에 맡기는 것과 같습니다. 금고 주인은 물건을 상자(Memento)에 담고 자물쇠를 채워 보관업체에 넘깁니다. 보관업체는 상자를 안전하게 보관만 할 뿐, 절대 자물쇠를 열고 그 안을 들여다보거나(Getter) 수정할 수 없습니다.
-
등장 배경 및 발전 과정:
- 캡슐화 파괴의 위험성: 상태 복원을 위해 객체의 모든 멤버 변수를 public이나 getter로 열어두면, 객체 지향의 근간인 정보 은닉이 파괴되어 누구나 객체의 상태를 오염시킬 수 있었다.
- 좁은 인터페이스와 넓은 인터페이스 (Two Interfaces): 자바의 패키지 접근 제어자(default, protected)나 C++의
friend키워드를 활용해, 상태를 보관하는Caretaker에게는 닫혀 있고(좁은 인터페이스), 상태를 만든Originator에게만 열려 있는(넓은 인터페이스) 메멘토 객체가 고안되었다.
-
📢 섹션 요약 비유: 게임 중간에 저장(세이브 파일)을 하면, 게임기(Originator)가 팩(Memento)을 만들어 서랍(Caretaker)에 넣어둡니다. 서랍은 팩을 보관할 뿐 내용을 수정할 수 없고, 나중에 팩을 다시 게임기에 꽂으면 그 순간으로 뿅 하고 돌아갑니다.
Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)
구성 요소 (클래스 다이어그램)
| 요소명 | 역할 | 비유 |
|---|---|---|
| Originator (원조자/생성자) | 자신의 현재 내부 상태를 바탕으로 Memento 객체를 생성(createMemento)하고, 나중에 전달받은 Memento를 통해 자신의 상태를 복원(restore)하는 주체. | 세이브 버튼을 누른 게임 캐릭터 |
| Memento (메멘토/기억) | Originator의 상태(변수들)를 캡슐화하여 저장하는 객체. 오직 생성한 Originator만이 내부 데이터에 접근할 수 있도록 권한이 통제된다. | 생성된 세이브 파일 덩어리 |
| Caretaker (보관자/관리자) | Memento 객체를 생성하도록 Originator에게 요청하고, 반환된 Memento들을 스택이나 리스트에 안전하게 보관하는 객체. 메멘토의 내용을 열어볼 수 없다. | 세이브 파일을 보관하는 클라우드나 메모리 카드 |
동작 메커니즘 (코드 뼈대 구조)
메멘토 패턴의 핵심은 **'이중 접근 제어(Double Interface)'**다. 자바(Java)의 경우 Memento 클래스를 Originator 내부의 private static class로 정의하거나 패키지 레벨(Default)로 제한하여 이중 권한을 구현한다.
┌─────────────────────────────────────────────────────────────┐
│ 메멘토 패턴의 상태 백업 및 복원 │
├─────────────────────────────────────────────────────────────┤
│ │
│ [Caretaker (히스토리 관리자)] │
│ - Stack<Memento> history; │
│ │ │
│ │ 1. "현재 상태 저장해!" ( origin.createMemento() 호출 ) │
│ ▼ │
│ [Originator (에디터/캐릭터)] ──── 2. 생성 ────▶ [Memento] │
│ - state : String - state : String│
│ (Getter 없음) │
│ │
│ │ 3. 반환받은 Memento를 history 스택에 push()로 보관. │
│ │
│ ───(시간이 흘러 에디터 내용이 망가진 후 Ctrl+Z 누름)────────────── │
│ │
│ │ 4. "이전 상태로 복원해!" ( history.pop() 꺼냄 ) │
│ │ 5. origin.restore(memento) 호출 │
│ ▼ │
│ [Originator] ◀────────────────── 6. 상태 덮어쓰기 [Memento] │
│ this.state = memento.state │
└─────────────────────────────────────────────────────────────┘
[다이어그램 해설] Caretaker(보관자)는 Originator(에디터)에게 상태 저장을 요구해 Memento를 건네받는다. 하지만 Caretaker는 이 Memento 객체 안에 뭐가 들었는지 볼 수 있는 getState() 메서드가 없다(컴파일 에러). 오직 스택에 보관만 할 수 있다. 나중에 복원을 위해 Memento를 다시 Originator에게 건네주면, 그제야 Originator는 자신의 것이었던 Memento 내부의 상태를 읽어와 현재 상태를 덮어쓴다. 완벽하게 캡슐화가 유지되는 백업 시스템이다.
커맨드(Command) 패턴과의 융합 시너지
메멘토 패턴이 가장 빛을 발하는 곳은 커맨드 패턴의 undo() 구현부다. 커맨드 패턴 단독으로는 "이전 좌표로 이동" 같은 단순 역연산은 가능하지만, 객체의 복잡한 전역 상태(예: 이미지 전체 픽셀)를 돌려놓기는 어렵다.
- 커맨드가
execute()되기 직전, 타겟 객체(Originator)의createMemento()를 호출해 상태를 통째로 백업받는다. - 커맨드 객체 내부에 이
Memento를 멤버 변수로 보관한다. (이때 커맨드가 Caretaker 역할을 겸한다.) undo()가 호출되면, 보관하던Memento를 타겟 객체로 넘겨 완벽한 복원을 수행한다.
- 📢 섹션 요약 비유: 커맨드 패턴이 "이전 페이지로 가기" 버튼(행동)이라면, 메멘토 패턴은 그 버튼을 눌렀을 때 나타날 이전 페이지의 "화면 캡처본(상태)"을 저장해 두는 기술입니다. 둘이 합쳐져야 완벽한 타임머신이 됩니다.
Ⅲ. 융합 비교 및 다각도 분석
1. 메멘토 패턴 vs 프로토타입 패턴 (Prototype Pattern)
둘 다 "현재 객체의 상태를 복제/저장한다"는 점이 비슷해 보이지만 쓰임새가 다르다.
| 비교 항목 | 메멘토 (Memento) | 프로토타입 (Prototype / Clone) |
|---|---|---|
| 핵심 목적 | 객체의 과거 상태를 저장했다가 나중에 현재 객체에 덮어써서 복원함 | 원본 객체를 본떠 새로운 객체(인스턴스)를 무한 복제함 |
| 객체의 동일성 | 객체(메모리 주소)는 1개이고, 그 안의 내부 상태값만 과거로 변함 | 원본과 완벽히 동일한 상태를 가진 별개의 객체가 N개 생성됨 |
| 은닉성 유지 | Caretaker가 복제본의 내용을 볼 수 없고 오직 보관만 함 | 복제된 새 객체를 클라이언트가 마음대로 조작하고 사용함 |
과목 융합 관점
-
데이터베이스 (DB): 트랜잭션의 롤백(Rollback) 메커니즘을 지원하기 위해, 커밋되기 전의 데이터를 보관해 두는 Undo Segment(오라클 기준)가 바로 메멘토 패턴의 시스템적 구현체다. 장애 발생 시 이 Segment의 데이터를 현재 데이터 파일에 덮어씌워 일관성을 회복한다.
-
클라우드 / IT 인프라: 가상머신(VM)이나 도커 컨테이너의 스냅샷(Snapshot) 생성 기능이 거대한 메멘토 패턴이다. 인프라 관리자(Caretaker)는 스냅샷(Memento) 파일 안에 램(RAM)과 디스크의 비트맵이 어떻게 얽혀있는지 알 필요 없이, 단순히 스냅샷 백업 및 롤백 명령만 수행한다.
-
📢 섹션 요약 비유: 프로토타입 패턴이 나와 똑같이 생긴 '복제인간'을 여러 명 만드는 기술이라면, 메멘토 패턴은 내 머릿속의 '기억'만 디스크에 뽑아뒀다가 나중에 내 머리에 다시 덮어쓰는(기억 상실 복원) 기술입니다.
Ⅳ. 실무 적용 및 기술사적 판단
실무 시나리오
-
시나리오 — 방대한 상태 백업으로 인한 메모리 초과(OOM): 웹 기반 그래픽 편집 툴을 만들었다. 사용자가 마우스를 드래그할 때마다 수백 MB짜리 캔버스의 전체 상태를 Memento로 만들어 Stack에 넣었다. 몇 분 지나지 않아 브라우저 탭의 메모리가 터져서(OOM) 프로그램이 강제 종료된다.
- 아키텍트의 해결책: 부분 상태 저장 (Delta Memento) 전략을 도입해야 한다. 전체 상태의 깊은 복사(Deep Copy)를 수행하는 대신, 직전 상태와의 차이점(Diff, 예: 이동한 레이어의 좌표 변화값만)을 저장하는 경량 메멘토로 진화시켜야 한다. Git의 커밋 히스토리가 증분(Delta)만 저장하여 거대한 소스 코드를 가볍게 백업하는 것과 같은 이치다.
-
시나리오 — MSA 환경에서의 복잡한 엔티티 이력(History) 추적: 회원 정보가 변경될 때마다 과거 이력을 DB에 쌓아 두어야 한다. 하지만 회원 도메인 로직 안에
HistoryRepository.save(new History(...))같은 DB 저장 로직이 덕지덕지 붙어 도메인이 인프라 계층에 강하게 결합(오염)되었다.- 아키텍트의 해결책: 메멘토 패턴을 활용하여 도메인 모델은
MemberMemento(또는MemberSnapshot) 객체를 생성해 반환(createSnapshot())하는 책이지만 진다. 외부의 애플리케이션 서비스(Caretaker)가 이 스냅샷을 받아 직렬화(JSON)한 뒤 이력 테이블에 저장한다. 도메인 모델은 자신이 어떻게 DB에 저장되는지 알 필요가 없어 완벽한 클린 아키텍처(Clean Architecture)가 유지된다.
- 아키텍트의 해결책: 메멘토 패턴을 활용하여 도메인 모델은
도입 체크리스트
- 기술적:
Caretaker가Memento의 내용을 훔쳐보지 못하게 자바 패키지 레벨 제어나private inner class를 완벽히 설정했는가? 자바스크립트/타입스크립트 환경이라면#접두사나 클로저(Closure)를 활용해 데이터 은닉을 강제해야 한다. - 운영적: 메멘토 객체를 파일이나 캐시(Redis)에 저장할 계획인가? 그렇다면 객체 내부의 모든 변수(깊은 객체 트리 포함)가 완벽하게 직렬화(Serializable) 가능한지 사전에 검증했는가?
안티패턴
-
얕은 복사(Shallow Copy)로 인한 메멘토 오염: 원본 객체가 내부 배열(
List)을 가지고 있을 때, 메멘토 객체에 그 배열의 참조 주소만 얕은 복사하여 넣는 실수. 나중에 원본 객체의 배열이 바뀌면 스택에 고이 모셔둔 메멘토의 배열 내용도 같이 바뀌어 버려 복원 시 끔찍한 버그가 터진다. 메멘토는 반드시 불변(Immutable) 객체로 만들어지거나 완벽한 깊은 복사(Deep Copy)를 거쳐야 한다. -
📢 섹션 요약 비유: 이사 갈 때 귀중품(상태)을 상자(메멘토)에 담아 보관소에 맡겼는데, 알고 보니 상자 밑바닥이 뚫려있어서(얕은 복사) 내 방의 물건을 움직이면 보관소의 물건도 같이 움직이는 황당한 오류를 조심해야 합니다.
Ⅴ. 기대효과 및 결론
정량/정성 기대효과
| 구분 | 속성 무단 접근 (Getter/Setter) 방식 | 메멘토 패턴 도입 후 | 개선 효과 |
|---|---|---|---|
| 정량 | 상태 복원을 위해 외부에 수십 개의 Getter 공개 | 1개의 restore(Memento) 메서드로 단일화 | 캡슐화 파괴(결합도)로 인한 버그 발생률 완전 제거 |
| 정량 | 외부 클래스가 원본의 변수 추가 시 모두 코드 수정 (O(N) 공수) | Memento와 Originator 사이에서만 수정 (O(1) 공수) | 모델 구조 변경 시 연쇄 수정 공수 80% 절감 |
| 정성 | 백업 데이터가 애플리케이션 곳곳에 더럽게 퍼져있음 | Caretaker라는 하나의 Stack 객체에서 통합 관리 | 메모리 관리 일원화 및 타임 트래블(Undo) 아키텍처 확립 |
미래 전망
- 상태 관리 라이브러리(Redux/Vuex)의 Time Travel Debugging: 현대 프론트엔드 환경에서 Redux Store의 상태(State) 트리는 언제나 '불변 객체(Immutable)'로 관리된다. 매 액션마다 생성되는 새로운 State 트리 자체가 거대한 Memento 객체 역할을 하며, 이를 Redux DevTools(Caretaker)가 배열로 쌓아두어 개발자가 시간 여행하듯 언제든 이전 렌더링 상태로 되돌릴 수 있는 혁명을 가져왔다.
- 이벤트 소싱(Event Sourcing) 시스템의 스냅샷: 분산 DB 설계 시, 모든 이벤트 로그(수만 개)를 처음부터 재연산(Replay)하여 현재 상태를 복구하면 속도가 너무 느리다. 그래서 주기적(예: 100개 이벤트마다)으로 현재 상태의 통째 복사본(Snapshot = Memento)을 저장해 두고, 그 스냅샷 지점부터 남은 이벤트만 연산하는 방식으로 분산 시스템의 복구 성능을 비약적으로 끌어올린다.
참고 표준
- GoF (Gang of Four): Behavioral Patterns - Memento
- Java Serialization:
java.io.Serializable을 통한 객체 직렬화 덤프 (메멘토 저장 방식의 일종) - Web Storage API: LocalStorage, SessionStorage를 활용한 클라이언트 폼 데이터 임시 저장 메커니즘
메멘토 패턴은 **"과거를 기억하되, 타인에게 내 기억의 구조를 노출하지 않는다"**는 아주 우아한 철학적 배경을 갖는다. 기술사는 상태를 저장하는 행위가 필연적으로 객체 지향의 캡슐화를 위협하는 모순(Paradox)임을 인지해야 한다. 그리고 메멘토 패턴이라는 이중 자물쇠(Double Interface)를 아키텍처에 심어둠으로써, 편의성(Undo/백업)과 보안성(정보 은닉)이라는 두 마리 토끼를 동시에 잡아내는 혜안을 발휘해야 한다.
- 📢 섹션 요약 비유: 스마트폰의 '전체 백업' 파일을 컴퓨터(Caretaker)에 저장해 두면 컴퓨터는 그 파일 안에 내 카톡 내용이 뭔지 몰라도(암호화/은닉), 나중에 새 폰을 샀을 때 완벽하게 옛날 폰 상태로 덮어쓰기(복원) 해주는 마법 같은 백업 기술입니다.
📌 관련 개념 맵 (Knowledge Graph)
| 개념 명칭 | 관계 및 시너지 설명 |
|---|---|
| 커맨드 패턴 (Command) | 메멘토를 활용하여 undo() 로직의 완벽한 롤백을 구현하는 가장 절친한 단짝 패턴. (커맨드는 행위 캡슐화, 메멘토는 상태 캡슐화) |
| 정보 은닉 (Information Hiding) | 메멘토 패턴이 존재하는 가장 근본적인 이유. 외부(Caretaker)가 객체의 내부 구조(변수)를 보지 못하게 차단하는 객체 지향 원칙. |
| 깊은 복사 (Deep Copy) | 원본 상태를 메멘토로 넘길 때, 참조 주소가 아닌 내부 데이터 덩어리 자체를 완벽히 복제하여 상태 오염을 막는 필수 구현 기술. |
| 스냅샷 (Snapshot) | VM, 컨테이너, 데이터베이스 등 인프라스트럭처 레벨에서 사용되는 메멘토 패턴의 시스템 확장 개념. |
| 이벤트 소싱 (Event Sourcing) | MSA 분산 환경에서 로그의 재생(Replay) 속도를 높이기 위해, 특정 시점의 전체 상태를 덤프해두는 스냅샷 아키텍처의 기반. |
👶 어린이를 위한 3줄 비유 설명
- 게임을 하다가 중간 보스 앞에서 '저장(Save)' 버튼을 누르면, 게임기(Originator)가 내 체력과 아이템 정보를 하나의 **팩(Memento)**으로 딱 만들어줘요.
- 여러분의 책상 서랍(Caretaker)은 이 팩을 얌전히 보관할 뿐, 팩을 열어서 체력을 마음대로 999로 수정할 수는 없어요. (열어볼 수 없게 잠겨있거든요!)
- 나중에 보스한테 져서 게임오버가 되면, 서랍에서 그 팩을 꺼내 다시 게임기에 꽂으면 딱 저장했던 그 순간으로 안전하게 돌아가는 마법, 이게 바로 **'메멘토 패턴'**이랍니다!