Pular para o conteúdo
GoF Padrões de Projeto Comportamentais
Comportamental escopo de objeto
GoF, p. 283 também chamado de Snapshot, Token

Lembrança

Memento

Salve e restaure o estado de um objeto sem violar seu encapsulamento.

Complexidade alta Frequência de uso rara
Intençãocomo o livro define

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.

Analogiauma imagem do mundo real

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.

O problema

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.

A solução

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.

Sintomascomo reconhecer no seu código
  • Getters públicos criados só para o código de “salvar estado”.
  • Cópias manuais de objeto espalhadas pelo código de undo.

Estrutura

salvar()guarda (não lê)cria/lêHistorico (Caretaker)- mementos[]+ backup()+ desfazer()Editor (Originator)- estado+ salvar(): Memento+ restaurar(m)Memento- estado (opaco)+ getData()
Fig. 18 — Memento herda / implementa cria usa / contém
Ver este diagrama sendo desenhado, traço a traço
Participantes
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

Listagem 18 Snapshots de um editor com histórico limitado
// ── 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
Na práticaonde ele já existe
  • java.io.Serializable e Object.clone()
  • javax.faces.component.StateHolder
  • Snapshots de máquina virtual; checkpoints de banco de dados; git stash

Consequências

A favor
  • 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.
Contra
  • 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.
Use quando
  • Você precisa de snapshots para desfazer, transações ou checkpoints.
  • Acessar campos diretamente quebraria o encapsulamento.
Evite quando
  • O estado é enorme e mudanças são frequentes — considere guardar apenas o delta (como faz o Command).

Relações com outros padrões

costuma andar junto
Command
A dupla clássica do undo: Command sabe O QUE mudou, Memento guarda COMO estava.
Iterator
Memento pode capturar o estado de um percurso.
alternativa a
Prototype
Se o objeto é simples e sem estado externo, clonar pode substituir o memento.
parecido / fácil de confundir
State
Ambos lidam com “estado”, mas Memento salva um instantâneo e State encapsula comportamento.

Verificação

Questãoa resposta está marcada

Por que o Caretaker não deve conseguir ler o conteúdo do Memento?

  1. Para preservar o encapsulamento: se ele lesse, ficaria acoplado à estrutura interna do Originator
  2. Por questões de segurança criptográfica
  3. Porque isso tornaria a operação mais lenta
  4. 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.