Introdução ao Padrão Memento
O Padrão Memento é classificado como um padrão de projeto comportamental. Seu principal objetivo é capturar e externalizar o estado interno de um objeto sem violar o princípio do encapsulamento, permitindo que o objeto seja restaurado para esse estado posteriormente.
Na prática, esse padrão atua como uma "máquina do tempo" para o estado de uma aplicação. Ele fornece um ponto de verificação (cehckpoint) para o qual o programa pode retroceder caso ocorra um erro ou se o usuário desejar desfazer uma ação. Exemplos clássicos incluem a funcionalidade de "Desfazer" (Ctrl+Z) em editores de texto, o sistema de salvar e carregar jogos (Save/Load) e o gerenciamento de estado de atividades no Android através dos métodos onSaveInstanceState e onRestoreInstanceState.
Implementação em Java
Para ilustrar o padrão, utilizaremos o cenário de um editor de documentos. A implementação envolve três componentes principais:
- Originator (Originador): O objeto cujo estado precisa ser salvo.
- Memento (Memorando): O objeto que armazena o estado interno do Originador.
- Caretaker (Cuidador): O objeto responsável por guardar e gerenciar o histórico dos memorandos, sem inspecionar ou modificar seu conteúdo.
1. Definindo o Memorando (Memento)
O memorando deve ser imutável após a criação, garantindo que o estado salvo não seja alterado acidentalmente. O construtor é restrito para manter o encapsulamento.
public class DocumentSnapshot {
private final String text;
private final int fontSize;
// Construtor com visibilidade de pacote para restringir a criação apenas ao Originador
DocumentSnapshot(String text, int fontSize) {
this.text = text;
this.fontSize = fontSize;
}
public String getText() {
return text;
}
public int getFontSize() {
return fontSize;
}
}
2. Definindo o Originador (Originator)
O originador contém a lógica para criar um memorando de seu estado atual e para restaurar seu estado a partir de um memorando fornecido.
public class Document {
private String text;
private int fontSize;
public Document(String text, int fontSize) {
this.text = text;
this.fontSize = fontSize;
}
public void updateText(String newText) {
this.text = newText;
}
public void updateFontSize(int newSize) {
this.fontSize = newSize;
}
public DocumentSnapshot createSnapshot() {
return new DocumentSnapshot(this.text, this.fontSize);
}
public void restore(DocumentSnapshot snapshot) {
this.text = snapshot.getText();
this.fontSize = snapshot.getFontSize();
}
@Override
public String toString() {
return "Conteúdo: '" + text + "' | Tamanho da Fonte: " + fontSize;
}
}
3. Definindo o Cuidador (Caretaker)
Para respeitar o Princípio de Demeter (Lei do Menor Conhecimento), o Cuidador gerencia o histórico dos estados sem precisar conhecer os detalhes internos do memorando.
import java.util.Stack;
public class HistoryManager {
private final Stack<DocumentSnapshot> history = new Stack<>();
public void saveState(Document document) {
history.push(document.createSnapshot());
}
public void undo(Document document) {
if (!history.isEmpty()) {
DocumentSnapshot previousState = history.pop();
document.restore(previousState);
}
}
}
4. Executando o Cleinte
public class Main {
public static void main(String[] args) {
Document doc = new Document("Rascunho inicial", 12);
HistoryManager manager = new HistoryManager();
System.out.println("Estado inicial: " + doc);
manager.saveState(doc);
doc.updateText("Primeira versão do artigo");
doc.updateFontSize(14);
System.out.println("Após edição: " + doc);
manager.saveState(doc);
doc.updateText("Texto com erros de digitação");
System.out.println("Edição incorreta: " + doc);
manager.undo(doc);
System.out.println("Após primeiro undo: " + doc);
manager.undo(doc);
System.out.println("Após segundo undo: " + doc);
}
}
Vantagens e Desvantagens
Vantagens
- Fornece um mecanismo robusto para recuperação de estado, permitindo que os usuários voltem a pontos anteriores de forma simples.
- Mantém o encapsulamento dos dados, pois o cliente (Cuidador) não precisa conhecer a estrutura interna dos estados salvos.
Desvantagens
- Alto consumo de recursos. Se o objeto originador possuir muitos atributos ou se os salvamentos forem muito frequentes, o uso de memória pode crescer significativamente devido à duplicação de estados.
Cenários de Uso
- Funcionalidades de "Desfazer" (Undo) e "Refazer" (Redo) em aplicações gráficas ou editores.
- Sistemas que exigem transações reversíveis ou pontos de verificação (checkpoints) em processos longos.
- Salvamento e carregamento de progresso em jogos ou simulações.
Considerações Importantes
- Utilize um Cuidador (Caretaker) para gerenciar o histórico, garantindo que a lógica de empilhamento e desempilhamento de estados fique isolada da lógica de negócio do Originador.
- Para otimizar o uso de memória em cenários com estados muito grandes, considere combinar o Padrão Memento com o Padrão Prototype, armazenando apenas as alterações (deltas) em vez de cópias completas do objeto.