반응형
Notice
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
Tags
- 빅데이터
- 데이터과학
- 프로그래밍언어
- 버전관리
- 네트워크보안
- 알고리즘
- 컴퓨터공학
- 데이터분석
- I'm Sorry
- 사이버보안
- 프로그래밍
- 네트워크
- 소프트웨어
- 컴퓨터과학
- 디자인패턴
- 웹개발
- Yes
- 소프트웨어공학
- 자바스크립트
- 컴퓨터비전
- 데이터베이스
- 인공지능
- springboot
- 데이터구조
- 보안
- 클라우드컴퓨팅
- 파이썬
- 자료구조
- 머신러닝
- 딥러닝
Archives
- Today
- Total
스택큐힙리스트
Memento 패턴: “되돌리기(Undo)”를 코드로 구현하는 가장 깔끔한 방법 본문
반응형
복잡한 앱을 만들다 보면 “방금 전 상태로 돌려줘!” 라는 요구가 꼭 생깁니다. 편집기에서의 Undo/Redo, 게임의 저장·로드, 그리고 상태 머신에서 특정 단계로 롤백 하기까지—모두 객체의 이전 상태를 안전하게 보관-복원하는 Memento 패턴으로 해결할 수 있습니다.
Memento는 캡슐화를 깨지 않고 객체의 스냅샷을 외부에 보관했다가 원할 때 정확히 그 상태로 되돌리는 행위(Behavioral) 패턴입니다.
언제, 왜 써야 할까?
- 상태 머신 롤백: 복잡한 워크플로에서 오류가 나면 직전 단계로 안전하게 복귀.
- 알림 시스템 취소: 푸시 예약을 “1분 안에 취소” 하도록 임시 스냅샷 보관.
- 에디터 Undo/Redo: 사용자가 편집할 때마다 스냅샷을 스택에 쌓았다가 단계별 복원.
핵심은 “상태 이력” 을 Caretaker가 관리하고, 객체 내부 구현은 건드리지 않는다는 점!
3개 핵심 역할만 기억하자
- Originator – 현재 상태를 갖고 스냅샷을 만들고 복원하는 주체
- Memento – 스냅샷 객체(불변·읽기 전용)
- Caretaker – 스냅샷들을 보관하고 필요할 때 Originator에게 돌려줌
Kotlin 실전 예제 – 글쓰기 화면 Undo/Redo
// --- Originator ---
data class EditorState(val text: String)
class TextEditor {
var content: String = ""
private set
fun write(newText: String) { content += newText }
fun save() = EditorState(content) // -> Memento
fun restore(memento: EditorState) { // <- Memento
content = memento.text
}
}
// --- Caretaker ---
class History {
private val undoStack = ArrayDeque<EditorState>()
private val redoStack = ArrayDeque<EditorState>()
fun push(state: EditorState) { undoStack.push(state); redoStack.clear() }
fun undo(current: EditorState): EditorState? =
if (undoStack.isEmpty()) null else undoStack.pop().also { redoStack.push(current) }
fun redo(current: EditorState): EditorState? =
if (redoStack.isEmpty()) null else redoStack.pop().also { undoStack.push(current) }
}
// --- 사용 예 ---
val editor = TextEditor()
val history = History()
editor.write("Hello, ")
history.push(editor.save())
editor.write("World!")
history.push(editor.save())
history.undo(editor.save())?.let { editor.restore(it) } // => "Hello, "
이처럼 Originator(TextEditor) 는 자신만 아는 내부 상태를 EditorState 에 담아 Caretaker(History)에 맡깁니다. Caretaker는 스택을 이용해 다단계 Undo/Redo를 구현하고, 텍스트 에디터는 오직 “쓰기·저장·복원” 만 알면 되므로 단일 책임이 지켜집니다.
JUnit 테스트 스케치
@Test
fun `undo restores previous content`() {
val editor = TextEditor()
val history = History()
editor.write("A").also { history.push(editor.save()) }
editor.write("B").also { history.push(editor.save()) }
val prev = history.undo(editor.save())
assertEquals("A", prev?.text)
}
간단하지만 핵심 로직(스냅샷 저장·복원)이 잘 동작하는지 검증할 수 있습니다.
장단점 짚어보기
- 👍 완벽한 캡슐화: Originator 내부를 외부가 엿보지 않아도 상태 저장 가능.
- 👍 간단한 복원 로직: 복구 시 side-effect 최소화—그냥 restore() 호출이면 끝.
- 👎 메모리 부담: 상태가 크거나 스냅샷이 잦으면 Caretaker 메모리 사용량 ↑.
- 👎 깊은 복사 주의: 얕은 복사로 참조만 넘기면 복원 시 예상치 못한 변경 위험.
한 줄 정리
Memento 패턴은 “캡슐화된 타임머신”—객체를 과거로 안전하게 되돌리고 싶을 때 가장 우아한 해법입니다.
반응형
'개발' 카테고리의 다른 글
Visitor 패턴: “구조는 그대로, 기능은 덧붙여” (3) | 2025.07.30 |
---|---|
이어보기 완성! Iterator + Memento로 구현한 플레이백 히스토리 (1) | 2025.07.30 |
MediatorLiveData 제대로 쓰는 7가지 실전 팁 (2) | 2025.07.30 |
Kotlin also 한눈에 이해하기 (1) | 2025.07.29 |
Mediator 패턴: 복잡한 객체 의존을 한 방에 정리하기 (2) | 2025.07.29 |
Comments