Memento
Introdução
Seção intitulada “Introdução”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.
Problema
Seção intitulada “Problema”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?
Solução
Seção intitulada “Solução”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:
- Originador: o objeto cujo estado precisa ser salvo e restaurado
- Memento: o retrato do estado capturado em determinado momento
- Cuidador: quem armazena os mementos e decide quando usá-los
O fluxo costuma funcionar assim:
- o cuidador pede ao originador que gere um memento
- o originador cria esse memento com acesso total ao próprio estado
- o cuidador armazena o memento sem alterar seu conteúdo
- quando necessário, o cuidador devolve o memento ao originador
- 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.
Analogia
Seção intitulada “Analogia”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.
Aplicabilidade
Seção intitulada “Aplicabilidade”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
Anti-padrão / Mau uso
Seção intitulada “Anti-padrão / Mau uso”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.
Como implementar
Seção intitulada “Como implementar”Uma implementação incremental pode seguir estes passos:
- identifique qual objeto será o originador
- defina quais partes do estado realmente precisam ser restauradas
- crie a classe
Mementocomo retrato desse estado - prefira tornar o memento imutável após a criação
- adicione no originador um método como
salvar()oucriarSnapshot() - adicione também um método como
restaurar(memento) - crie um cuidador para manter a pilha ou lista de histórico
- 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.
Exemplo em código
Seção intitulada “Exemplo em código”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
Contras
Seção intitulada “Contras”- 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
Relações com outros padrões/conceitos
Seção intitulada “Relações com outros padrões/conceitos”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.
Implementações alternativas (quando aplicável)
Seção intitulada “Implementações alternativas (quando aplicável)”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
undoeredo - 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.
Exemplo completo
Seção intitulada “Exemplo completo”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.