Módulo 10: Paradigmas de Programação & Padrões de Projeto • Bloco 4: Padrões GoF Comportamentais

🏛️ Transações Reversíveis, Filas e Snapshots de Memória: Como transformar operações computacionais voláteis em objetos duráveis que podem ser enfileirados, serializados, auditados e desfeitos? Neste capítulo, exploramos dois padrões complementares do Gang of Four que fundamentam os sistemas transacionais modernos: o Command (que encapsula a ação a ser executada) e o Memento (que encapsula o estado a ser restaurado sem quebrar o encapsulamento).


🗺️ Mapa Conceitual do Capítulo

flowchart TD
    COMPORT3["Padrões Comportamentais (Transações & Snapshots)"]
    
    COMPORT3 --> CMD["1. Command (Comando / Ação Encapsulada)<br/>• Transforma uma requisição em um objeto de 1ª classe<br/>• Desacopla Invoker do Receiver<br/>• Viabiliza filas assíncronas, macros e pilhas de Undo/Redo"]
    
    COMPORT3 --> MEM["2. Memento (Lembrança / Snapshot Imutável)<br/>• Captura o estado interno sem expor atributos privados<br/>• Originator (gera/restaura) vs Caretaker (armazena)<br/>• Checkpoints, Rollbacks e Delta Compression"]

    COMPORT3 --> SYN["3. Sinergia Command + Memento<br/>• O Command armazena o Memento antes de executar<br/>• O método undo() restaura o memento com custo determinístico"]

    style COMPORT3 fill:#fce4ec,stroke:#c2185b,stroke-width:2px
    style CMD fill:#e8f5e9,stroke:#4caf50,stroke-width:1.5px
    style MEM fill:#e1f5fe,stroke:#03a9f4,stroke-width:1.5px
    style SYN fill:#fffde7,stroke:#fbc02d,stroke-width:1.5px

🕹️ 1. O Padrão Command: Ações como Cidadãos de Primeira Classe

“Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.” — GoF (1994)

1.1 A Dissociação entre Gatilho e Execução

Na programação procedural ou orientada a eventos tradicional, o botão de uma interface gráfica chama diretamente o método da regra de negócio: btnSalvar.onClick = banco.salvarUsuario(). Isso gera problemas imediatos:

  • Impossível reutilizar o mesmo comando em um atalho de teclado (Ctrl+S), em um menu de contexto e em um botão da barra de ferramentas sem duplicar código;
  • Impossível implementar operações de “Desfazer” (Undo) e “Refazer” (Redo);
  • Impossível colocar a requisição em uma fila em segundo plano para execução agendada.

O padrão Command soluciona isso interpondo um objeto intermediário que implementa uma interface contratual com método executar() e, opcionalmente, desfazer():

classDiagram
    class Invocador {
        -historico: List~ICommand~
        +definirComando(ICommand) void
        +acionar() void
        +desfazer() void
    }
    class ICommand {
        <<interface>>
        +executar() void
        +desfazer() void
    }
    class ConcreteCommand {
        -receptor: ServicoFinanceiroReceiver
        -parametros: DadosTransacao
        +executar() void
        +desfazer() void
    }
    class ServicoFinanceiroReceiver {
        +creditar(conta, valor) void
        +debitar(conta, valor) void
    }

    Invocador o--> ICommand : Dispara
    ICommand <|.. ConcreteCommand
    ConcreteCommand --> ServicoFinanceiroReceiver : Delega ação real

1.2 Os Quatro Grandes Casos de Uso do Command na Indústria

  1. Pilhas de Desfazer e Refazer (Undo/Redo): O invocador gerencia duas pilhas (UndoStack e RedoStack). Ao executar, empilha o comando executado; ao clicar em “Desfazer”, desempilha e chama cmd.desfazer().
  2. Filas de Tarefas Assíncronas (Background Job Queues): Em sistemas modernos com Celery (Python), Sidekiq (Ruby) ou Hangfire (C#), uma requisição web cria um objeto ProcessarVideoCommand, serializa-o em JSON e o joga no Redis. Trabalhadores em segundo plano (Workers) desserializam e invocam .executar().
  3. Write-Ahead Logging (WAL) para Tolerância a Falhas: Bancos de dados e brokers gravam cada comando em um log sequencial em disco antes de aplicar na memória. Se a energia cair, o banco lê o log e reexecuta os comandos (Replay).
  4. Macro-Comandos (Composite Commands): Um único comando que agrupa uma sequência de dezenas de outros comandos subordinados executados em lote de forma transacional.

💾 2. O Padrão Memento: Snapshots sem Quebra de Encapsulamento

“Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.” — GoF (1994)

2.1 Por que Métodos Get/Set Públicos Destroem o Design?

Se uma classe EditorDocumento precisa que seu estado seja salvo periodicamente, a solução ingênua seria expor todos os seus campos privados via getters públicos para que um gerenciador de histórico salve em uma lista. Isso viola fatalmente o encapsulamento:

  • Qualquer classe externa passa a ter acesso aos detalhes internos de implementação do documento;
  • Se a estrutura interna mudar (ex.: de string para árvore de nós de formatação), o gerenciador de histórico quebra.

2.2 O Segredo do Memento: Interfaces Estreita e Ampla

O padrão divide as responsabilidades em três participantes rigorosos:

  1. Originador (Originator): O objeto de negócio cujo estado precisa ser salvo. Ele cria novos mementos contendo seu estado atual (criar_memento()) e sabe como restaurar seu estado interno a partir de um memento (restaurar(memento)).
  2. Memento: O objeto imutável que encapsula a foto instantânea (snapshot) dos dados. Ele possui uma Interface Estreita (Narrow Interface) para o mundo exterior (não expõe nenhum método de leitura ou modificação) e uma Interface Ampla (Wide Interface) acessível exclusivamente pelo Originador.
  3. Guardião (Caretaker): O repositório histórico (ex.: gerenciador de checkpoints ou transações). Ele apenas guarda e devolve mementos opacos, sem jamais ler, inspecionar ou tentar alterar seu conteúdo interno.
sequenceDiagram
    autonumber
    actor Cliente
    participant O as Originador (EditorTexto)
    participant C as Caretaker (GerenciadorCheckpoints)
    participant M as Memento (Snapshot Imutável)

    Cliente->>O: digitar("Capítulo 15")
    Cliente->>O: criarCheckpoint()
    O->>M: new Memento("Capítulo 15")
    O->>C: guardarMemento(M)
    Note over C: O Caretaker guarda o Memento<br/>como uma caixa-preta opaca
    Cliente->>O: digitar(" - Erro de digitação!")
    Note over Cliente: O usuário solicita desfazer
    Cliente->>C: obterUltimoMemento()
    C-->>Cliente: M
    Cliente->>O: restaurar(M)
    O->>M: extrairEstadoInterno()
    Note over O: Estado restaurado para: "Capítulo 15"

⚡ 3. Sinergia Arquitetural: Command + Memento

Quando combinados, esses dois padrões formam a fundação definitiva de sistemas transacionais e de computação resiliente:

  • Cada comando que altera o modelo de domínio captura um Memento do Originator imediatamente antes de aplicar a alteração:
    def executar(self):
        self._memento_anterior = self._origem.criar_memento()
        self._origem.modificar(...)
    
    def desfazer(self):
        self._origem.restaurar(self._memento_anterior)
    
  • Isso elimina a necessidade de escrever algoritmos matemáticos complexos e propensos a erros para “reverter” operações: o estado simplesmente retorna à foto exata tirada momentos antes.

📊 4. Matriz Comparativa Sistemática: Command vs. Memento vs. Strategy

Dimensão de Análise Command Memento Strategy
Foco Central Ação / Intenção (o que fazer) Estado / Snapshot (o que o objeto é) Algoritmo (como calcular)
Temporalidade Orientado ao futuro (ações pendentes, filas) Orientado ao passado (histórico, pontos de restauração) Orientado ao presente (troca dinâmica imediata)
Reversibilidade Implementa método formal desfazer() Fornece a imagem de memória para a restauração Geralmente irreversível por si só
Encapsulamento Conhece o receptor e aciona seus métodos Oculta dados internos através de interface opaca Recebe dados do contexto via parâmetros