Pular para o conteúdo

O Memento é um padrão de projeto comportamental usado para salvar e restaurar estados anteriores de um objeto sem quebrar seu encapsulamento.

Ele costuma aparecer quando o sistema precisa oferecer funcionalidades como desfazer, rollback, histórico de edição ou recuperação de estado. A ideia central é simples: o próprio objeto dono do estado cria um retrato seguro de si mesmo, e outro objeto apenas guarda esse retrato para uso futuro.

Esse padrão é especialmente importante porque resolver esse tipo de problema de forma ingênua quase sempre leva a uma escolha ruim: ou o sistema expõe detalhes internos demais, ou fica incapaz de restaurar o estado corretamente.

Imagine um editor de texto acadêmico usado para montar relatórios, resumos e exercícios. O editor mantém vários dados internos:

  • conteúdo do texto
  • posição do cursor
  • trecho selecionado
  • nível de zoom
  • posição de rolagem
  • modo atual de formatação

Agora surge um requisito comum: permitir que o usuário desfaça a última ação.

Uma primeira solução parece direta: antes de cada alteração, algum componente externo copia os dados do editor e salva em um histórico. Depois, quando necessário, esse histórico devolve o último estado salvo.

O problema é que essa abordagem costuma esbarrar em questões importantes:

  • o estado relevante do objeto pode estar em campos privados
  • outras classes passam a depender da estrutura interna do objeto
  • qualquer refatoração interna obriga mudanças em quem copia o estado
  • o encapsulamento fica comprometido
  • o histórico pode ganhar acesso a dados que nunca deveria manipular

O problema central é este: como guardar um retrato confiável de um objeto sem obrigar outras partes do sistema a conhecer seus detalhes internos?

O Memento resolve isso transferindo para o próprio objeto a responsabilidade de criar o retrato do seu estado.

Nesse padrão, normalmente aparecem três papéis:

  1. Originador: o objeto cujo estado precisa ser salvo e restaurado
  2. Memento: o retrato do estado capturado em determinado momento
  3. Cuidador: quem armazena os mementos e decide quando usá-los

O fluxo costuma funcionar assim:

  1. o cuidador pede ao originador que gere um memento
  2. o originador cria esse memento com acesso total ao próprio estado
  3. o cuidador armazena o memento sem alterar seu conteúdo
  4. quando necessário, o cuidador devolve o memento ao originador
  5. o originador restaura seu estado a partir daquele retrato

Com isso, o histórico não precisa conhecer a estrutura interna do objeto. Ele apenas guarda snapshots produzidos pelo próprio dono do estado.

Pense em um jogo com sistema de salvamento rápido.

O jogador aperta “salvar”, e o jogo registra um retrato do momento atual: posição, inventário, vida e progresso. Mais tarde, se algo der errado, o jogo carrega esse estado salvo.

Quem sabe exatamente quais dados precisam ser armazenados é o próprio jogo, não o menu que lista os saves. O menu apenas guarda e organiza os arquivos. No Memento, o mesmo raciocínio vale para o cuidador e para o originador.

Use Memento quando:

  • você precisa restaurar estados anteriores de um objeto
  • a aplicação oferece funcionalidade de desfazer ou rollback
  • o acesso direto ao estado interno violaria encapsulamento
  • há necessidade de manter histórico de alterações relevantes
  • o objeto possui estado suficiente para justificar snapshots explícitos

Exemplos reais em software:

  • editor de texto com undo
  • sistema de desenho com histórico de alterações
  • formulários complexos com restauração de versão anterior
  • workflow que precisa desfazer etapas após erro
  • jogos com pontos de salvamento ou checkpoints

O Memento costuma ser mal utilizado quando a equipe passa a salvar estados com frequência excessiva ou sem critério.

Erros comuns:

  • capturar o objeto inteiro a cada pequena mudança sem avaliar custo
  • guardar snapshots grandes demais por tempo indefinido
  • usar Memento quando um estado simples poderia ser recalculado
  • permitir que o cuidador manipule internamente o conteúdo do memento
  • ignorar o impacto de memória e armazenamento

O resultado pode ser um histórico pesado, consumo excessivo de RAM e código mais complexo do que o problema exigia. Memento faz sentido quando o benefício de restaurar o estado compensa o custo de mantê-lo.

Uma implementação incremental pode seguir estes passos:

  1. identifique qual objeto será o originador
  2. defina quais partes do estado realmente precisam ser restauradas
  3. crie a classe Memento como retrato desse estado
  4. prefira tornar o memento imutável após a criação
  5. adicione no originador um método como salvar() ou criarSnapshot()
  6. adicione também um método como restaurar(memento)
  7. crie um cuidador para manter a pilha ou lista de histórico
  8. defina política de descarte para snapshots antigos, se necessário

Antes de implementar, vale perguntar: o sistema realmente precisa voltar a estados anteriores, e esse estado não pode ser reconstruído de forma simples? Se a resposta for sim, Memento pode ser a abordagem correta.

No exemplo abaixo, um editor de notas salva snapshots antes de alterações importantes.

import java.util.Stack;
class EditorTexto {
private String conteudo;
private int posicaoCursor;
public EditorTexto(String conteudo, int posicaoCursor) {
this.conteudo = conteudo;
this.posicaoCursor = posicaoCursor;
}
public void escrever(String novoConteudo) {
this.conteudo = novoConteudo;
this.posicaoCursor = novoConteudo.length();
}
public Memento criarSnapshot() {
return new Memento(conteudo, posicaoCursor);
}
public void restaurar(Memento memento) {
this.conteudo = memento.getConteudo();
this.posicaoCursor = memento.getPosicaoCursor();
}
public void exibir() {
System.out.println("Conteudo: " + conteudo + " | Cursor: " + posicaoCursor);
}
public static class Memento {
private final String conteudo;
private final int posicaoCursor;
private Memento(String conteudo, int posicaoCursor) {
this.conteudo = conteudo;
this.posicaoCursor = posicaoCursor;
}
private String getConteudo() {
return conteudo;
}
private int getPosicaoCursor() {
return posicaoCursor;
}
}
}
class HistoricoEditor {
private final Stack<EditorTexto.Memento> historico = new Stack<>();
public void salvar(EditorTexto editor) {
historico.push(editor.criarSnapshot());
}
public void desfazer(EditorTexto editor) {
if (!historico.isEmpty()) {
editor.restaurar(historico.pop());
}
}
}

Nesse código, EditorTexto é o originador, Memento representa o snapshot, e HistoricoEditor atua como cuidador. O histórico sabe quando salvar e quando desfazer, mas não precisa conhecer os detalhes internos do editor.

  • preserva o encapsulamento do originador
  • facilita implementação de desfazer e rollback
  • separa a responsabilidade de guardar histórico da lógica principal do objeto
  • torna explícita a ideia de snapshot de estado
  • pode simplificar coordenação de recuperação em fluxos complexos
  • pode consumir muita memória se houver muitos snapshots
  • adiciona novas classes e fluxo extra de coordenação
  • exige cuidado para definir o que entra no snapshot
  • pode complicar gerenciamento de ciclo de vida do histórico
  • em alguns casos, copiar estado completo pode ser caro demais

O Memento se relaciona com outros padrões importantes:

  • Command: é comum combinar Memento com Command para implementar desfazer em operações encapsuladas
  • Prototype: Prototype clona objetos; Memento captura estado para restauração, com foco em histórico
  • Iterator: pode usar Memento para registrar e restaurar posição de navegação
  • Encapsulamento: o padrão existe justamente para preservar fronteiras de acesso ao estado interno
  • Imutabilidade: mementos imutáveis são mais seguros para representar snapshots confiáveis

Essas relações ajudam a perceber que o Memento não serve para criar novas instâncias por conveniência, mas para registrar e restaurar estados anteriores com segurança.

Algumas variações comuns incluem:

  • memento como classe aninhada do originador
  • memento exposto por interface limitada para o cuidador
  • histórico em pilha para undo
  • dois históricos separados para undo e redo
  • snapshots completos ou diferenciais, dependendo do custo

Em aplicações maiores, também pode existir integração com persistência em banco, cache temporário ou checkpoints distribuídos. Ainda assim, a ideia principal continua a mesma: capturar um retrato seguro e restaurável do estado.

Agora veja um exemplo mais próximo de um sistema acadêmico. Um editor de feedback de correção permite escrever observações, mover o cursor e desfazer a última alteração.

import java.util.Stack;
class EditorFeedback {
private String texto;
private int cursor;
private String comentarioSelecionado;
public EditorFeedback() {
this.texto = "";
this.cursor = 0;
this.comentarioSelecionado = "";
}
public void definirTexto(String texto) {
this.texto = texto;
this.cursor = texto.length();
}
public void selecionarComentario(String comentarioSelecionado) {
this.comentarioSelecionado = comentarioSelecionado;
}
public void moverCursor(int cursor) {
this.cursor = cursor;
}
public Snapshot salvar() {
return new Snapshot(texto, cursor, comentarioSelecionado);
}
public void restaurar(Snapshot snapshot) {
this.texto = snapshot.texto;
this.cursor = snapshot.cursor;
this.comentarioSelecionado = snapshot.comentarioSelecionado;
}
public void exibirEstado() {
System.out.println(
"Texto='" + texto +
"' | cursor=" + cursor +
" | comentario='" + comentarioSelecionado + "'"
);
}
public static class Snapshot {
private final String texto;
private final int cursor;
private final String comentarioSelecionado;
private Snapshot(String texto, int cursor, String comentarioSelecionado) {
this.texto = texto;
this.cursor = cursor;
this.comentarioSelecionado = comentarioSelecionado;
}
}
}
class HistoricoFeedback {
private final Stack<EditorFeedback.Snapshot> pilha = new Stack<>();
public void salvar(EditorFeedback editor) {
pilha.push(editor.salvar());
}
public void desfazer(EditorFeedback editor) {
if (!pilha.isEmpty()) {
editor.restaurar(pilha.pop());
}
}
}
public class Main {
public static void main(String[] args) {
EditorFeedback editor = new EditorFeedback();
HistoricoFeedback historico = new HistoricoFeedback();
editor.definirTexto("Aluno demonstrou boa compreensão do tema.");
editor.selecionarComentario("versao-inicial");
historico.salvar(editor);
editor.definirTexto("Aluno demonstrou boa compreensão, mas precisa melhorar a justificativa.");
editor.moverCursor(25);
editor.selecionarComentario("versao-revisada");
editor.exibirEstado();
historico.desfazer(editor);
editor.exibirEstado();
}
}

Nesse exemplo, o HistoricoFeedback controla o momento de salvar e desfazer, enquanto EditorFeedback continua responsável por definir quais partes do seu estado realmente importam para restauração. Esse desenho ajuda a implementar histórico sem expor diretamente os campos internos do editor.