"게임에서 보스전을 앞두고 '저장'을 합니다. 죽으면 다시 불러와야 하니까요."
"문서 작업을 하다가 실수로 내용을 지웠습니다. Ctrl+Z를 눌러 이전 상태로 되돌립니다."
이 기능을 구현하려면 객체의 현재 상태(private 필드값)를 어딘가에 저장해야 합니다. 하지만 저장하기 위해 Getter/Setter를 남발하면 캡슐화가 깨집니다. 누군가 저장된 데이터를 멋대로 조작할 수도 있죠.
이 딜레마를 해결하기 위해 "저장된 상태(메멘토)"를 누구에게 어디까지 보여줄 것인가를 결정하는 것이 이 패턴의 핵심입니다.
1. 방식 1: 화이트박스 메멘토 (White-box Memento) - "순진한 방식"
저장된 상태 객체(Memento)의 내부를 **공개(Public)**하는 방식입니다. 구현이 쉽지만, 상태를 관리하는 쪽(Caretaker)이 데이터를 읽거나 조작할 수 있어 안전하지 않습니다.
특징
- 메멘토 객체가 일반 DTO처럼 동작합니다.
- 외부에서 상태 값을 마음대로 바꿀 위험이 있습니다.
코드 예시
// 1. 메멘토 (저장 상자) - 내부가 다 보임!
class EditorMemento {
private String content; // 상태
public EditorMemento(String content) { this.content = content; }
// Getter/Setter가 있어서 누구나 내용을 볼 수 있음
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
}
// 2. 오리진 (원본 객체)
class Editor {
private String content;
public void setContent(String content) { this.content = content; }
// 저장
public EditorMemento createMemento() {
return new EditorMemento(content);
}
// 복원
public void restore(EditorMemento memento) {
this.content = memento.getContent();
}
}
// 3. 케어테이커 (관리자)
class History {
private Stack<EditorMemento> stack = new Stack<>();
public void push(EditorMemento m) {
// 문제점: 관리자가 내용을 훔쳐보거나 조작 가능!
System.out.println("저장된 내용: " + m.getContent());
stack.push(m);
}
// ... pop 구현
}
2. 방식 2: 블랙박스 메멘토 (Black-box Memento) - "GoF의 정석"
자바의 내부 클래스(Inner Class) 특징을 활용하여, "생성한 사람(Editor) 외에는 내부를 절대 볼 수 없게" 만드는 방식입니다.
특징
- Memento는 마커 인터페이스(빈 껍데기)로만 외부에 노출됩니다.
- 실제 상태는 private static inner class에 숨겨져 있어 외부(History)에서는 접근이 불가능합니다.
코드 예시
// 1. 외부에 노출할 껍데기 인터페이스 (내용물 접근 메서드 없음)
interface Memento {}
// 2. 오리진 (에디터)
class Editor {
private String content;
public void setContent(String content) { this.content = content; }
// 저장: 내부 클래스 생성
public Memento createMemento() {
return new EditorMemento(this.content);
}
// 복원: 파라미터로 받은 껍데기를 내부 클래스로 캐스팅해서 사용
public void restore(Memento memento) {
if (!(memento instanceof EditorMemento)) {
throw new IllegalArgumentException("잘못된 메멘토입니다.");
}
EditorMemento em = (EditorMemento) memento;
this.content = em.content;
}
// 3. 핵심: 내부 클래스 (private으로 선언하여 Editor만 접근 가능)
private static class EditorMemento implements Memento {
private final String content; // 불변성 보장
private EditorMemento(String content) {
this.content = content;
}
// Getter 없음! 외부에서 절대 읽을 수 없음.
}
}
// 4. 케어테이커 (관리자)
class History {
private Stack<Memento> stack = new Stack<>();
public void push(Memento m) {
stack.push(m);
// m.getContent() -> 호출 불가! (메서드가 없음)
// 관리자는 이 객체가 뭔지 모르고 그냥 보관만 함 (캡슐화 성공)
}
public Memento pop() {
return stack.pop();
}
}
장단점
- 장점: 완벽한 캡슐화. History 클래스는 자신이 무엇을 저장하는지조차 모른 채 보관만 수행합니다. (가장 객체지향적인 방식)
- 단점: 내부 클래스 문법을 사용해야 하므로 코드가 약간 복잡해 보일 수 있습니다.
3. 실무 예시: 언제 쓰이나요?
1) 텍스트 에디터 / IDE의 Undo
우리가 코드를 짜다가 Ctrl+Z를 누르면 이전 상태로 돌아갑니다. 이때 IDE는 수많은 파일의 변경 내역을 메멘토 객체로 스택에 쌓아둡니다.
2) 데이터베이스 트랜잭션 롤백
DB는 트랜잭션 수행 전의 상태를 '로그'나 '스냅샷' 형태로 저장해 둡니다. 만약 에러가 발생하면 저장해 둔 메멘토(스냅샷)를 이용해 원래 상태로 복구(Rollback)합니다.
요약: 보여줄 것인가, 숨길 것인가?
| 구분 | White-box Memento (Naive) | Black-box Memento (Secure) |
| 구조 | 별도의 Public 클래스 | Private Inner Class |
| 접근 제어 | Getter/Setter로 누구나 접근 가능 | 외부 접근 불가 (인터페이스만 노출) |
| 안전성 | 낮음 (관리자가 데이터 조작 가능) | 높음 (생성자만 데이터 해석 가능) |
| 구현 난이도 | 쉬움 (일반 DTO와 같음) | 중간 (내부 클래스 및 인터페이스 활용) |
결론
단순히 데이터를 임시 저장하는 것이 목적이고, 프로젝트 규모가 작다면 화이트박스 방식도 나쁘지 않습니다.
하지만 중요한 비즈니스 데이터를 저장해야 하거나, 상태 관리자(History)가 데이터에 의존하지 않게(결합도를 낮추게) 만들고 싶다면 반드시 블랙박스 메멘토 패턴을 사용하세요. 이것이 진정한 객체지향의 정보 은닉입니다.
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 널 객체 패턴(Null Object Pattern) 총정리 (0) | 2025.12.07 |
|---|---|
| 인터프리터 패턴(Interpreter Pattern) 완벽 정리 (0) | 2025.12.07 |
| 플라이웨이트 패턴(Flyweight Pattern) 완벽 정리 (0) | 2025.12.07 |
| 방문자 패턴(Visitor Pattern) 완벽 정리 (0) | 2025.12.07 |
| 브리지 패턴(Bridge Pattern) 완벽 정리 (0) | 2025.12.07 |