GoF, p. 283 também chamado de Snapshot, Token
Lembrança
Memento
Salve e restaure o estado de um objeto sem violar seu encapsulamento.
Sem violar o encapsulamento, capturar e externalizar o estado interno de um objeto, de modo que ele possa ser restaurado a esse estado mais tarde.
O save do videogame
Antes do chefão, você salva. O arquivo de save é opaco — você não abre e edita a vida do personagem no bloco de notas. Só o jogo sabe ler aquele formato. Se morrer, você carrega e volta exatamente ao ponto anterior.
Para implementar desfazer, você precisa de fotografias do estado do objeto. Mas se o cliente tiver que ler todos os campos para copiá-los, você é obrigado a tornar tudo público — destruindo o encapsulamento e amarrando o histórico à estrutura interna, que vai mudar.
O próprio objeto (Originator) produz um Memento: um objeto opaco que contém a fotografia do estado. O Caretaker guarda mementos, mas não consegue lê-los; só sabe devolvê-los ao Originator para restaurar.
- Getters públicos criados só para o código de “salvar estado”.
- Cópias manuais de objeto espalhadas pelo código de undo.
Estrutura
- Originator
- Cria mementos com seu estado e sabe restaurar-se a partir deles.
- Memento
- Objeto de valor imutável e opaco com a fotografia do estado.
- Caretaker
- Guarda a pilha de mementos; nunca inspeciona o conteúdo.
Implementação
// ── Memento: opaco por construção (campo privado) ─────
class Memento {
#estado;
constructor(estado) { this.#estado = structuredClone(estado); Object.freeze(this); }
// Só o Originator usa isto. O Caretaker enxerga apenas os metadados abaixo.
_restaurar() { return structuredClone(this.#estado); }
get rotulo() { return this.#estado.rotulo ?? 'snapshot'; }
}
// ── Originator: quem tem o estado sabe salvá-lo ───────
class EditorDeTexto {
#estado = { texto: '', cursor: 0, selecao: null, rotulo: 'vazio' };
digitar(t) {
this.#estado.texto += t;
this.#estado.cursor = this.#estado.texto.length;
this.#estado.rotulo = `"${t.slice(0, 12)}"`;
}
get texto() { return this.#estado.texto; }
salvar() { return new Memento(this.#estado); }
restaurar(m) { this.#estado = m._restaurar(); }
}
// ── Caretaker: guarda, mas não entende o conteúdo ─────
class Historico {
#pilha = [];
constructor(editor, limite = 50) { this.editor = editor; this.limite = limite; }
backup() {
this.#pilha.push(this.editor.salvar());
if (this.#pilha.length > this.limite) this.#pilha.shift();
}
desfazer() { const m = this.#pilha.pop(); if (m) this.editor.restaurar(m); }
get linha() { return this.#pilha.map(m => m.rotulo); } // metadados, não o estado
}
const editor = new EditorDeTexto();
const hist = new Historico(editor);
hist.backup(); editor.digitar('Era uma vez ');
hist.backup(); editor.digitar('um padrão ');
hist.backup(); editor.digitar('chamado Memento.');
console.log(editor.texto); // Era uma vez um padrão chamado Memento.
console.log(hist.linha); // [ 'vazio', '"Era uma vez "', '"um padrão "' ]
hist.desfazer(); console.log(editor.texto); // Era uma vez um padrão
hist.desfazer(); console.log(editor.texto); // Era uma vez - java.io.Serializable e Object.clone()
- javax.faces.component.StateHolder
- Snapshots de máquina virtual; checkpoints de banco de dados; git stash
Consequências
- Snapshots sem violar o encapsulamento do objeto.
- Simplifica o Originator: ele não precisa manter o próprio histórico.
- O formato interno pode mudar sem afetar o Caretaker.
- Consome muita memória se os clientes criarem mementos com frequência.
- O Caretaker precisa destruir mementos obsoletos.
- Linguagens sem encapsulamento forte não conseguem garantir a opacidade do memento.
- Você precisa de snapshots para desfazer, transações ou checkpoints.
- Acessar campos diretamente quebraria o encapsulamento.
- O estado é enorme e mudanças são frequentes — considere guardar apenas o delta (como faz o Command).
Relações com outros padrões
- Prototype
- Se o objeto é simples e sem estado externo, clonar pode substituir o memento.
- State
- Ambos lidam com “estado”, mas Memento salva um instantâneo e State encapsula comportamento.
Verificação
Por que o Caretaker não deve conseguir ler o conteúdo do Memento?
- Para preservar o encapsulamento: se ele lesse, ficaria acoplado à estrutura interna do Originator
- Por questões de segurança criptográfica
- Porque isso tornaria a operação mais lenta
- Porque o memento é sempre serializado em disco
A opacidade é o coração do padrão. Se o histórico entendesse o formato do estado, mudar um campo do Originator quebraria o histórico — exatamente o que se quis evitar.