ComportamentalIntermediário6 min de leitura9 seções1994, Marco de referência: a ideia é mais velha que a data

Memento

Captura o estado interno de um objeto num instantâneo opaco que pode ser guardado e restaurado depois, sem expor os detalhes desse estado a quem o guarda.

Em 10 segundos

O objeto entrega um instantâneo de si mesmo que só ele sabe ler. O histórico guarda essa caixa-preta e devolve quando pedido — assim dá para voltar no tempo sem que ninguém de fora conheça os campos internos.

  • #snapshot
  • #undo
  • #encapsulamento
  • #gof
Analogia

O save do videogame

Quando você salva o jogo antes do chefão, o console grava um arquivo que só o jogo sabe interpretar — posição, vida, inventário, tudo num formato interno. O sistema de arquivos guarda aquilo como um monte de bytes e não faz ideia do que significa. Se o jogo mudar como representa o inventário na próxima versão, o sistema de arquivos não precisa saber de nada.

01Conceito

O problema

Voltar atrás exige saber como era antes. A forma ingênua é o histórico ler os campos do objeto e guardá-los — o que expõe exatamente o que o encapsulamento protegia.

Quando isso acontece, o objeto congela: mudar um campo interno quebra o histórico, o teste do histórico e às vezes o formato salvo em disco.

A saída do padrão é inverter quem produz o instantâneo. O objeto se conhece, então é ele quem captura e quem restaura; o zelador só transporta. Isso preserva a fronteira e ainda deixa o objeto livre para mudar sua representação, desde que continue capaz de ler os mementos antigos.

A relação com Command é de complemento, não de concorrência. O Command guarda a intenção ('colar este texto aqui'); o Memento guarda o estado ('o documento estava assim'). Comandos com inverso exato e barato dispensam memento; comandos destrutivos (ordenar, formatar, substituir tudo) precisam dele para desfazer corretamente.

Há também um parentesco conceitual com Event Sourcing: lá, o estado é reconstruído reaplicando eventos, e os snapshots existem justamente para não reaplicar tudo desde o começo — snapshots que são, em essência, mementos.

02Conceito

Como manter o memento realmente opaco

A opacidade é o coração do padrão e a parte que mais se perde na prática, porque poucas linguagens oferecem o controle de visibilidade que o GoF assumia.

Em linguagens com classes aninhadas privadas (Java, C#), o memento pode ser uma classe interna do originador: só ele enxerga os campos, e o zelador recebe apenas uma interface vazia de marcação.

Em TypeScript e Python, a opacidade é convenção reforçada por tipo: o zelador recebe o memento tipado como algo sem membros úteis, e o originador faz o cast de volta. Não é impenetrável, mas comunica a intenção — e o teste que importa é se algum código fora do originador lê os campos.

O critério prático: se você precisar mudar um campo interno do objeto e isso obrigar a mexer no zelador, a opacidade já foi perdida, independentemente do que digam os modificadores de acesso.

Como se encaixa
  1. Editor
  2. salvar() capturaMemento (opaco)
  3. guarda sem lerHistórico
  4. devolve → restaurar()Editor restaurado
O instantâneo sai do editor e volta para o editor. O histórico é só um depósito no meio do caminho — ele nunca abre a caixa.
Sem o padrão × com o padrão

Com Memento: o objeto se retrata

  • o editor produz e consome o próprio instantâneo
  • os campos continuam privados
  • mudar a representação interna não afeta o histórico
  • o zelador guarda caixas-pretas indistintas

O custo é memória: cada instantâneo copia o estado relevante, e um histórico longo de objetos grandes pesa — daí os limites de profundidade e os snapshots espaçados.

A troca é encapsulamento por espaço. Quando o estado é pequeno, é quase de graça; quando é grande, o padrão continua certo mas pede política de retenção.
Passo a passoO fluxo, passo a passo
  1. 1

    Originador captura

    O objeto produz um memento com o mínimo necessário para restaurar — campos continuam privados.

  2. 2

    Zelador guarda

    O histórico empilha o instantâneo sem ler o conteúdo (caixa-preta).

  3. 3

    Estado muda

    O originador segue editando; o passado fica congelado no memento.

  4. 4

    Restaurar

    O zelador devolve o memento; só o originador sabe remontar o estado a partir dele.

03Anatomia

Navegue pelas camadas

Camada 1/3Originador

Camada 1

Originador

captura e restaura a si mesmo

É o único que conhece a estrutura do memento. Decide o que entra no instantâneo — normalmente o mínimo necessário para voltar ao estado, não o objeto inteiro.

No código

salvar() { return new MementoEditor(this.texto, this.cursor) }

Se violar: originador que aceita restaurar a partir de um objeto montado por terceiros abre a porta para estados inválidos que nenhuma regra criou.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

// Memento: opaco para fora, legivel so pelo originador
class MementoEditor {
  constructor(
    readonly texto: string,
    readonly cursor: number,
  ) {}
}

class Editor {
  private texto = "";
  private cursor = 0;

  digitar(t: string): void {
    this.texto += t;
    this.cursor = this.texto.length;
  }

  // captura: um instantaneo do que importa
  salvar(): MementoEditor {
    return new MementoEditor(this.texto, this.cursor);
  }

  // restauracao: so o originador sabe remontar seu estado
  restaurar(m: MementoEditor): void {
    this.texto = m.texto;
    this.cursor = m.cursor;
  }

  toString(): string {
    return `"${this.texto}" (cursor ${this.cursor})`;
  }
}

// Zelador: guarda e devolve, nunca inspeciona
class Historico {
  private pilha: MementoEditor[] = [];
  guardar(m: MementoEditor): void {
    this.pilha.push(m);
  }
  voltar(): MementoEditor | undefined {
    return this.pilha.pop();
  }
}

const ed = new Editor();
const h = new Historico();
h.guardar(ed.salvar());
ed.digitar("olá mundo");
const antes = h.voltar();
if (antes) ed.restaurar(antes);
console.log(ed.toString()); // "" (cursor 0)
06No mundo real

Casos de uso

Formulário longo com 'descartar alterações'

Cenário

Um cadastro com cinquenta campos e várias abas permite editar à vontade e, no fim, descartar tudo e voltar ao que estava salvo.

Como o padrão entra

Ao abrir a edição, o formulário captura um memento do estado inicial. 'Descartar' restaura esse instantâneo de uma vez, sem precisar reverter campo a campo nem recarregar do servidor.

Trade-off: Se outra pessoa alterou o registro nesse meio-tempo, restaurar o instantâneo local ressuscita dados velhos ao salvar — o memento precisa carregar a versão do registro para detectar o conflito.

Checkpoint antes de operação em lote

Cenário

Um reajuste de preços altera milhares de produtos e, se algo der errado no meio, é preciso voltar ao estado anterior sem restaurar backup do banco inteiro.

Como o padrão entra

Antes de começar, o serviço captura mementos dos itens afetados e os guarda. Uma falha dispara a restauração dos instantâneos, revertendo apenas o que foi tocado.

Trade-off: Memória e tempo crescem com o tamanho do lote, e o instantâneo envelhece: alterações concorrentes feitas durante a operação são desfeitas junto, sem que ninguém tenha pedido isso.

Snapshot em Event Sourcing

Cenário

Uma conta com duzentos mil eventos levaria segundos para ser reconstruída do zero a cada consulta.

Como o padrão entra

A cada N eventos o agregado produz um memento do seu estado. A reconstrução carrega o último instantâneo e aplica só os eventos posteriores.

Trade-off: O snapshot vira formato persistido e ganha o problema de versionamento: mudar o estado interno do agregado exige conseguir ler os instantâneos antigos ou descartá-los e reprocessar tudo.

O histórico que bisbilhota o editor

Compila, roda, passa no code review. E quebra depois.

Em vez do editor entregar um memento opaco, o histórico lê e escreve os campos públicos. O encapsulamento morreu; a estrutura interna virou contrato do undo.

class Editor {
  texto = "";
  cursor = 0;
  digitar(t: string) { this.texto += t; this.cursor = this.texto.length; }
}

class Historico {
  private pilha: { texto: string; cursor: number }[] = [];

  // O zelador bisbilhota os campos do originador.
  guardar(ed: Editor) {
    this.pilha.push({ texto: ed.texto, cursor: ed.cursor });
  }

  voltar(ed: Editor) {
    const m = this.pilha.pop();
    if (!m) return;
    ed.texto = m.texto;
    ed.cursor = m.cursor;
  }
}

// Campos internos viraram API do historico.
// Renomear cursor no Editor quebra o Historico.

O que quebra

  • Ao renomear um campoO histórico (e qualquer snapshot em disco) quebra junto — o interno virou API.
  • Ao evoluir o estadoCada campo novo precisa ser lembrado em dois lugares: originador e zelador.
  • Na revisãoCampos públicos 'para o undo' são o cheiro de memento invertido.

A saída: Originador expõe salvar() / restaurar(m). Zelador só guarda e devolve. Se a UI precisa de rótulo no histórico, o originador fornece um resumo — não abre a caixa.

08Cuidado

Armadilhas

  1. Cópia rasa que compartilha referência

    Guardar this.itens num memento sem copiar a lista significa guardar um ponteiro para a lista viva. O objeto continua alterando aquela mesma lista, e o 'instantâneo' acompanha as mudanças. Na hora de restaurar, nada volta — e o bug é dos mais difíceis de enxergar, porque o código parece certo.

  2. Histórico sem teto

    Um memento por tecla digitada, guardados indefinidamente, é vazamento de memória com outro nome. Toda pilha de desfazer precisa de política: limite de profundidade, agrupamento de ações próximas no tempo ou instantâneos espaçados com comandos entre eles.

  3. Zelador que aprende a ler

    Basta uma necessidade — 'quero mostrar na interface o texto de cada ponto do histórico' — para alguém abrir o memento no zelador. Feito isso, o encapsulamento acabou e o padrão virou uma classe a mais sem benefício. Se o histórico precisa exibir algo, o originador deve fornecer um rótulo junto, não abrir a caixa.

09Decisão

Quando usar × quando evitar

Use quando

3
  • Você precisa de desfazer, checkpoint ou descarte de alterações.
  • O estado a preservar é interno e não deve ser exposto a quem guarda.
  • Uma operação arriscada precisa de ponto de retorno.

Evite quando

3
  • O estado é grande e muda a todo instante.
  • A operação tem inverso exato e barato — guarde a operação, não o estado.
  • O objeto já é imutável: o valor anterior é o próprio instantâneo.

Testar o que ficou

3 armadilhas de Memento viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • Time travel do Redux DevToolsCada estado é guardado inteiro para poder voltar — snapshot opaco, restaurável.
  • Undo de qualquer editorO estado anterior é salvo sem expor a estrutura interna de quem o produziu.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Snapshot opaco para restaurar depois.
const snap = editor.salvar();
editor.restaurar(snap);

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Guardar snapshots consome memória proporcional ao tamanho e à frequência dos estados salvos
  • O memento precisa capturar estado suficiente para restaurar, sem vazar os internos do objeto

Não vale se: o estado é trivial de reconstruir ou não precisa de undo — aí salvar snapshots é peso morto.

Costuma ser confundido com

Onde isto aparece

Continue por aqui