메멘토 패턴
개요
편집 전 상태로 돌아가려면 예전 내용을 보관해야 한다. 이력을 관리하는 쪽이 편집기의 내부 필드를 하나씩 읽고 다시 채우면, 편집기의 구조를 바꿀 때 이력 코드도 깨진다.
메멘토(Memento)는 상태를 보관하는 스냅샷이다. 상태를 가진 원본 객체가 저장과 복원을 맡고, 보관자(Caretaker)는 내용을 해석하지 않은 채 스냅샷을 가지고 있는다.
UML
TextEditor가 비공개 Snapshot을 만들고 ISnapshot으로 반환한다. 보관자는 그 핸들을 가지고 있다가 복원 시 돌려준다. 상태를 읽고 쓰는 책임은 TextEditor에 남는다.
역사
GoF는 1994년 Design Patterns에서 Memento를 행위 패턴으로 정리했다. 객체의 캡슐화를 유지하면서 나중에 복원할 상태를 저장하는 방식이다.1
왜 사용하는가?
이력 관리 코드가 편집기의 내부 표현을 알 필요가 없다. 복사할 상태가 늘어나면 원본 객체의 저장·복원 코드에서 함께 관리한다.
스냅샷을 계속 보관하면 메모리를 사용한다. 보관 개수와 수명, 어떤 상태까지 되돌릴지를 정하며 이미 전송한 메일이나 외부 저장소의 변경까지 자동 취소한다고 보지 않는다.
예제
초안을 저장한 뒤 내용을 바꾸고, 저장한 상태로 복원한다.
using System;
using System.Collections.Generic;
using System.Linq;
var editor = new TextEditor();
editor.Write("draft");
ISnapshot saved = editor.Save();
editor.Write("edited");
Console.WriteLine(editor.Restore(saved));
Console.WriteLine(editor.Text);
interface ISnapshot { }
sealed class TextEditor
{
private readonly Guid id = Guid.NewGuid();
private sealed record Snapshot(Guid Owner, string Text) : ISnapshot;
public string Text { get; private set; } = "";
public void Write(string text) => Text = text;
public ISnapshot Save() => new Snapshot(id, Text);
public bool Restore(ISnapshot snapshot)
{
if (snapshot is not Snapshot saved || saved.Owner != id) return false;
Text = saved.Text;
return true;
}
}
True
draft호출부는 ISnapshot에서 문자열을 꺼내지 않는다. TextEditor가 자기 스냅샷을 받아 상태를 돌린다.
범위 넓힌 예제
다른 편집기에 같은 스냅샷을 전달하면 거절하도록 원본 식별자를 검사한다.
using System;
using System.Collections.Generic;
using System.Linq;
var editor = new TextEditor();
editor.Write("draft");
ISnapshot saved = editor.Save();
var other = new TextEditor();
other.Write("other");
Console.WriteLine(other.Restore(saved));
Console.WriteLine(other.Text);
editor.Write("edited");
Console.WriteLine(editor.Restore(saved));
Console.WriteLine(editor.Text);
interface ISnapshot { }
sealed class TextEditor
{
private readonly Guid id = Guid.NewGuid();
private sealed record Snapshot(Guid Owner, string Text) : ISnapshot;
public string Text { get; private set; } = "";
public void Write(string text) => Text = text;
public ISnapshot Save() => new Snapshot(id, Text);
public bool Restore(ISnapshot snapshot)
{
if (snapshot is not Snapshot saved || saved.Owner != id) return false;
Text = saved.Text;
return true;
}
}
False
other
True
draft잘못된 원본의 스냅샷은 false를 반환하고 수신 편집기를 바꾸지 않는다. 예제는 문자열 하나의 메모리 상태만 복원하며, 스냅샷 형식의 파일 저장이나 버전 호환은 별도 문제다.
같이 보기
각 예제는 독립된 .NET 10 콘솔 프로그램이다.