
코틀린 Memento 패턴은 undo, draft 복원, 상태 되돌리기처럼 사용자는 자주 원하지만 구현은 의외로 헷갈리는 기능을 설명할 때 매우 좋은 패턴입니다. 특히 상태를 저장하고 싶지만 캡슐화는 깨고 싶지 않은 상황을 다룬다는 점이 핵심입니다.
이번 글에서는 Memento 패턴을 단순 정의로 보지 않고, 왜 편집기나 폼 상태 복원 같은 장면에서 자주 등장하는지, 코틀린에서는 data class snapshot과 immutable state 감각으로 어떻게 더 읽기 쉽게 풀 수 있는지까지 같이 정리하겠습니다.

기본 개념 참고로는 Refactoring.Guru의 Memento 패턴 설명, 코틀린 구현 감각으로는 Kotlin data classes 문서를 참고했습니다. 이번 글은 그 내용을 코틀린 상태 모델링 감각으로 다시 풀어 설명합니다.
왜 이 패턴이 필요할까
가장 대표적인 상황은 undo입니다. 텍스트를 수정하거나 폼 값을 바꾸기 전에 이전 상태를 저장해 두고, 사용자가 취소를 누르면 그때 상태를 되돌리고 싶어집니다.
문제는 이 상태를 외부에서 마구 꺼내 읽게 만들면 캡슐화가 깨진다는 점입니다. 내부 필드를 전부 공개하면 snapshot은 만들기 쉬워지지만, 이후 구조가 바뀔 때 외부 코드가 모두 같이 흔들리게 됩니다.
Memento 패턴은 이 문제를 우회합니다. 상태를 가장 잘 아는 객체가 자기 상태를 저장 가능한 형태로 만들고, 외부는 그 저장물 자체를 들고 있다가 필요할 때 복원만 요청하는 흐름입니다.
대표 예시: 편집기와 폼 상태 복원
이 패턴이 자주 편집기 예시와 함께 설명되는 이유는 직관적이기 때문입니다. 문서를 수정하기 전 snapshot을 하나 저장하고, undo를 누르면 가장 최근 snapshot으로 되돌리면 됩니다.
실무적으로는 필터 패널, 폼 입력 draft, 단계형 설정 화면에서도 비슷한 구조가 나옵니다. 사용자가 임시로 상태를 바꾸다가 취소하면 이전 스냅샷을 복원해야 하는 장면입니다.

코틀린 코드로 보면 어떻게 생길까
data class EditorMemento(
val text: String,
val cursor: Int,
)
class Editor(
private var text: String,
private var cursor: Int,
) {
fun save(): EditorMemento = EditorMemento(text, cursor)
fun restore(memento: EditorMemento) {
text = memento.text
cursor = memento.cursor
}
fun update(newText: String, newCursor: Int) {
text = newText
cursor = newCursor
}
}
class History {
private val stack = ArrayDeque<EditorMemento>()
fun push(memento: EditorMemento) {
stack.addLast(memento)
}
fun pop(): EditorMemento? =
if (stack.isEmpty()) null else stack.removeLast()
}이 예제에서는 Editor가 자기 상태를 save()로 snapshot화하고, History는 그 snapshot을 저장만 합니다. 즉 History는 내부 구조를 해석하지 않고, Editor만 restore를 통해 자기 상태를 다시 되돌립니다.
코틀린에서는 이런 구조를 data class로 비교적 읽기 쉽게 만들 수 있습니다. 다만 data class copy는 얕은 복사라는 점은 꼭 기억해야 합니다. 상태 안에 mutable 컬렉션이나 참조형 객체가 깊게 들어 있으면, 단순 copy만으로는 완전한 독립 snapshot이 안 될 수 있습니다.
Command 패턴과는 무엇이 다를까
undo를 다룬다는 점 때문에 Memento와 Command는 자주 같이 언급됩니다. 하지만 초점이 다릅니다. Command는 요청 자체를 객체로 만들고, Memento는 상태 snapshot을 저장하는 쪽에 더 가깝습니다.
즉 Command는 “무슨 행동을 실행할까”에 가깝고, Memento는 “이전 상태를 어떻게 복원할까”에 가깝습니다. 그래서 undo를 명령 역연산으로 풀 수도 있고, snapshot 복원으로 풀 수도 있습니다.

이 비교는 앞선 Command 패턴 글과도 연결됩니다. 상태 자체를 저장할지, 행동을 객체로 저장할지는 undo 요구사항의 성격에 따라 달라질 수 있습니다.
코틀린다운 포인트는 무엇일까
코틀린에서는 전통적인 패턴 설명보다 더 가볍게 이 구조를 풀 수 있습니다. immutable state + data class snapshot + history stack 조합만으로도 핵심 의도를 잘 드러낼 수 있기 때문입니다.
특히 UI 상태가 data class로 관리되는 경우라면 이전 상태를 통째로 저장하는 구조가 더 자연스럽게 읽힐 수 있습니다. 그래서 Memento 패턴은 오래된 패턴이지만, 코틀린에서는 오히려 더 직관적으로 보일 때가 있습니다.
다만 상태가 커질수록 snapshot 비용도 함께 커집니다. 예를 들어 대형 텍스트 문서, 이미지 편집 상태, 큰 컬렉션이 포함된 객체는 매번 전체를 복사하는 비용이 부담이 될 수 있습니다. 이때는 diff 저장이나 부분 복원처럼 다른 전략을 검토해야 합니다.
언제 잘 맞고, 언제 과할까
Memento는 사용자가 “되돌리기”를 기대하는 기능에서 특히 잘 맞습니다. 편집기, 폼 draft, 설정 화면, 필터 변경 취소처럼 이전 상태 복원이 UX 핵심일 때 가치가 큽니다.
하지만 상태가 너무 자주 바뀌고 snapshot이 너무 무거우면 오히려 저장 비용과 history 관리 비용이 커집니다. 결국 사용 경험을 좋게 하려다 메모리와 복잡도가 동시에 늘어날 수 있습니다.

실전에서 스스로 던질 질문
- 사용자가 이전 상태 복원을 실제로 자주 기대하는가
- snapshot 크기가 감당 가능한 수준인가
- 외부가 내부 필드를 직접 만지지 않고도 복원 구조를 유지할 수 있는가
- 행동 기반 undo보다 상태 기반 undo가 더 자연스러운가
이 질문에 yes가 많아질수록 Memento 패턴 후보일 가능성이 높습니다. 반대로 복원보다 단순 재조회/재계산이 더 쉬운 구조라면 굳이 snapshot history를 둘 이유가 약해질 수 있습니다.
마무리
Memento 패턴의 핵심은 snapshot 객체를 하나 더 만드는 데 있지 않습니다. 객체 상태를 저장하고 복원해야 할 때, 그 상태의 소유권과 캡슐화를 어떻게 지킬지에 대한 설계 선택에 있습니다.
즉 undo와 상태 복원이 필요하지만 내부 구현은 외부에 드러내고 싶지 않은 상황이 보이면, Memento 패턴은 꽤 실용적인 선택지가 됩니다. 코틀린에서는 data class와 immutable state 감각 덕분에 이 구조를 더 직관적으로 풀 수 있다는 점도 큰 장점입니다.
코틀린 디자인 패턴 시리즈
코틀린 디자인 패턴 시리즈(0) – 왜 아직도 디자인 패턴을 배워야 할까
코틀린 디자인 패턴(1) – Singleton 패턴은 언제 쓰고 object는 어떻게 다를까
코틀린 디자인 패턴(2) – Factory Method 패턴으로 생성 책임 나누기
코틀린 디자인 패턴(3) – Abstract Factory 패턴은 언제 필요할까
코틀린 디자인 패턴(4) – Builder 패턴과 named argument는 어떻게 다를까
코틀린 디자인 패턴(5) – Prototype 패턴과 data class copy 정리
코틀린 디자인 패턴(6) – Adapter 패턴으로 기존 코드를 새 인터페이스에 맞추기
코틀린 디자인 패턴(7) – Bridge 패턴은 상속 폭발을 어떻게 줄일까
코틀린 디자인 패턴(8) – Composite 패턴으로 트리 구조 다루기
코틀린 디자인 패턴(9) – Decorator 패턴은 상속 대신 어떻게 확장할까
코틀린 디자인 패턴(10) – Facade 패턴으로 복잡한 시스템 감추기
코틀린 디자인 패턴(11) – Flyweight 패턴은 메모리를 어떻게 아낄까
코틀린 디자인 패턴(12) – Proxy 패턴은 Decorator와 무엇이 다를까
코틀린 디자인 패턴(13) – Chain of Responsibility 패턴으로 조건 분기 줄이기
코틀린 디자인 패턴(14) – Command 패턴으로 실행과 취소를 다루는 법
코틀린 디자인 패턴(15) – Interpreter 패턴은 어디까지 써야 할까