Padrão de Projeto Memento: Gerenciamento e Restauração de Estado

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.

Tags: memento-pattern design-patterns behavioral-patterns state-management java

Publicado em 6-12 17:57 por Thomas