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.
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.
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.
- Editor
- salvar() capturaMemento (opaco)
- guarda sem lerHistórico
- devolve → restaurar()Editor restaurado
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.
- 1
Originador captura
O objeto produz um memento com o mínimo necessário para restaurar — campos continuam privados.
- 2
Zelador guarda
O histórico empilha o instantâneo sem ler o conteúdo (caixa-preta).
- 3
Estado muda
O originador segue editando; o passado fica congelado no memento.
- 4
Restaurar
O zelador devolve o memento; só o originador sabe remontar o estado a partir dele.
Navegue pelas camadas
Camada 1/3Originador
use ← →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.
Estrutura
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)from dataclasses import dataclass
# Memento: dados congelados, sem comportamento
@dataclass(frozen=True)
class MementoEditor:
texto: str
cursor: int
class Editor:
def __init__(self):
self._texto = ""
self._cursor = 0
def digitar(self, t):
self._texto += t
self._cursor = len(self._texto)
# captura
def salvar(self):
return MementoEditor(self._texto, self._cursor)
# restauracao: so o originador remonta seu estado
def restaurar(self, m):
self._texto = m.texto
self._cursor = m.cursor
def __str__(self):
return f'"{self._texto}" (cursor {self._cursor})'
# Zelador: guarda e devolve, nunca inspeciona
class Historico:
def __init__(self):
self._pilha = []
def guardar(self, m):
self._pilha.append(m)
def voltar(self):
return self._pilha.pop() if self._pilha else None
ed, h = Editor(), Historico()
h.guardar(ed.salvar())
ed.digitar("ola mundo")
ed.restaurar(h.voltar())
print(ed) # "" (cursor 0)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.
Armadilhas
Cópia rasa que compartilha referência
Guardar
this.itensnum 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.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.
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.
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
- CommandTransforma uma ação em objeto, com os dados de que ela precisa. Isso permite enfileirar, agendar, registrar e desfazer operações — e desacopla quem pede de quem executa.
- Event SourcingEm vez de guardar o estado atual, o sistema guarda a sequência imutável de eventos que o produziu — o estado é sempre derivado por replay, e o histórico é a fonte da verdade.
- StateTransforma cada estado de um objeto em uma classe própria: o comportamento muda conforme o estado atual, e é o próprio estado que decide a próxima transição.