Паттерн Memento (Снимок) позволяет сохранять и восстанавливать предыдущее состояние объекта, не раскрывая деталей его реализации. Он часто используется для реализации операций «Отмена» (Undo), восстановления состояния или отката к предыдущей версии данных.
Когда применять Memento
- Когда нужно сохранять состояние объекта для последующего восстановления.
- Когда прямое сохранение состояния нарушает инкапсуляцию.
- Когда важно реализовать историю изменений (например, редакторы, игры, базы данных).
PlantUML-диаграмма
// Снимок (Memento)
public class EditorMemento
{
public string Text { get; }
public int CursorPosition { get; }
public DateTime Timestamp { get; }
public EditorMemento(string text, int cursorPosition)
{
Text = text;
CursorPosition = cursorPosition;
Timestamp = DateTime.Now;
}
}
// Создатель (Originator)
public class TextEditor
{
private string _text = "";
private int _cursor = 0;
public void Type(string newText)
{
_text += newText;
_cursor = _text.Length;
Console.WriteLine($"Текущий текст: {_text}");
}
public EditorMemento Save()
{
Console.WriteLine("Состояние сохранено.");
return new EditorMemento(_text, _cursor);
}
public void Restore(EditorMemento memento)
{
_text = memento.Text;
_cursor = memento.CursorPosition;
Console.WriteLine($"Состояние восстановлено: {_text}");
}
}
// Хранитель (Caretaker)
public class History
{
private Stack<EditorMemento> _history = new();
public void Push(EditorMemento memento) => _history.Push(memento);
public EditorMemento Pop() => _history.Pop();
}
// Клиентский код
class Program
{
static void Main()
{
var editor = new TextEditor();
var history = new History();
editor.Type("Привет, ");
history.Push(editor.Save());
editor.Type("мир!");
history.Push(editor.Save());
editor.Type(" Как дела?");
// Откат
editor.Restore(history.Pop());
editor.Restore(history.Pop());
}
}
// Снимок: поля неэкспортируемые, снаружи состояние не подглядеть.
type EditorMemento struct {
text string
cursor int
timestamp time.Time
}
type TextEditor struct {
text string
cursor int
}
func (e *TextEditor) Type(newText string) {
e.text += newText
e.cursor = len([]rune(e.text))
fmt.Printf("Текущий текст: %s\n", e.text)
}
func (e *TextEditor) Save() EditorMemento {
fmt.Println("Состояние сохранено.")
return EditorMemento{text: e.text, cursor: e.cursor, timestamp: time.Now()}
}
func (e *TextEditor) Restore(m EditorMemento) {
e.text = m.text
e.cursor = m.cursor
fmt.Printf("Состояние восстановлено: %s\n", e.text)
}
type History struct {
items []EditorMemento
}
func (h *History) Push(m EditorMemento) { h.items = append(h.items, m) }
func (h *History) Pop() EditorMemento {
last := h.items[len(h.items)-1]
h.items = h.items[:len(h.items)-1]
return last
}
func main() {
editor := &TextEditor{}
history := &History{}
editor.Type("Привет, ")
history.Push(editor.Save())
editor.Type("мир!")
history.Push(editor.Save())
editor.Type(" Как дела?")
editor.Restore(history.Pop())
editor.Restore(history.Pop())
}
from dataclasses import dataclass, field
from datetime import datetime
@dataclass(frozen=True)
class EditorMemento:
text: str
cursor_position: int
timestamp: datetime = field(default_factory=datetime.now)
class TextEditor:
def __init__(self) -> None:
self._text = ""
self._cursor = 0
def type(self, new_text: str) -> None:
self._text += new_text
self._cursor = len(self._text)
print(f"Текущий текст: {self._text}")
def save(self) -> EditorMemento:
print("Состояние сохранено.")
return EditorMemento(self._text, self._cursor)
def restore(self, memento: EditorMemento) -> None:
self._text = memento.text
self._cursor = memento.cursor_position
print(f"Состояние восстановлено: {self._text}")
class History:
def __init__(self) -> None:
self._history: list[EditorMemento] = []
def push(self, memento: EditorMemento) -> None:
self._history.append(memento)
def pop(self) -> EditorMemento:
return self._history.pop()
editor = TextEditor()
history = History()
editor.type("Привет, ")
history.push(editor.save())
editor.type("мир!")
history.push(editor.save())
editor.type(" Как дела?")
editor.restore(history.pop())
editor.restore(history.pop())
class EditorMemento {
readonly timestamp = new Date();
constructor(
readonly text: string,
readonly cursorPosition: number,
) {}
}
class TextEditor {
private text = "";
private cursor = 0;
type(newText: string): void {
this.text += newText;
this.cursor = this.text.length;
console.log(`Текущий текст: ${this.text}`);
}
save(): EditorMemento {
console.log("Состояние сохранено.");
return new EditorMemento(this.text, this.cursor);
}
restore(memento: EditorMemento): void {
this.text = memento.text;
this.cursor = memento.cursorPosition;
console.log(`Состояние восстановлено: ${this.text}`);
}
}
class History {
private readonly history: EditorMemento[] = [];
push(memento: EditorMemento): void {
this.history.push(memento);
}
pop(): EditorMemento | undefined {
return this.history.pop();
}
}
const editor = new TextEditor();
const history = new History();
editor.type("Привет, ");
history.push(editor.save());
editor.type("мир!");
history.push(editor.save());
editor.type(" Как дела?");
const first = history.pop();
if (first) editor.restore(first);
const second = history.pop();
if (second) editor.restore(second);
import java.time.LocalDateTime;
import java.util.ArrayDeque;
import java.util.Deque;
record EditorMemento(String text, int cursorPosition, LocalDateTime timestamp) {
EditorMemento(String text, int cursorPosition) {
this(text, cursorPosition, LocalDateTime.now());
}
}
class TextEditor {
private String text = "";
private int cursor = 0;
void type(String newText) {
text += newText;
cursor = text.length();
System.out.println("Текущий текст: " + text);
}
EditorMemento save() {
System.out.println("Состояние сохранено.");
return new EditorMemento(text, cursor);
}
void restore(EditorMemento memento) {
text = memento.text();
cursor = memento.cursorPosition();
System.out.println("Состояние восстановлено: " + text);
}
}
class History {
private final Deque<EditorMemento> history = new ArrayDeque<>();
void push(EditorMemento memento) {
history.push(memento);
}
EditorMemento pop() {
return history.pop();
}
}
public class Program {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
History history = new History();
editor.type("Привет, ");
history.push(editor.save());
editor.type("мир!");
history.push(editor.save());
editor.type(" Как дела?");
editor.restore(history.pop());
editor.restore(history.pop());
}
}
import java.time.LocalDateTime
data class EditorMemento(
val text: String,
val cursorPosition: Int,
val timestamp: LocalDateTime = LocalDateTime.now(),
)
class TextEditor {
private var text = ""
private var cursor = 0
fun type(newText: String) {
text += newText
cursor = text.length
println("Текущий текст: $text")
}
fun save(): EditorMemento {
println("Состояние сохранено.")
return EditorMemento(text, cursor)
}
fun restore(memento: EditorMemento) {
text = memento.text
cursor = memento.cursorPosition
println("Состояние восстановлено: $text")
}
}
class History {
private val history = ArrayDeque<EditorMemento>()
fun push(memento: EditorMemento) = history.addLast(memento)
fun pop(): EditorMemento? = history.removeLastOrNull()
}
fun main() {
val editor = TextEditor()
val history = History()
editor.type("Привет, ")
history.push(editor.save())
editor.type("мир!")
history.push(editor.save())
editor.type(" Как дела?")
history.pop()?.let { editor.restore(it) }
history.pop()?.let { editor.restore(it) }
}
Плюсы и минусы
| Плюсы | Минусы |
|---|---|
| Позволяет сохранять и восстанавливать состояние без нарушения инкапсуляции | Потребляет больше памяти при частом сохранении состояния |
| Упрощает реализацию «Отмены» и «Повтора» | Сложность управления историей и временем хранения снимков |
| Хорошо работает с сериализацией и логированием | Не подходит для очень больших объектов (много данных в снимке) |
Заключение
Memento — поведенческий паттерн, который помогает реализовать систему восстановления состояния объектов. Он особенно полезен в текстовых редакторах, играх, undo/redo механизмах и других сценариях, где важно сохранить историю изменений.