Design de software

Command vs Memento: duas abordagens para implementar undo

Você já travou ao tentar criar uma função de undo por conta própria?

4 min de leitura
Imagem de capa de Command vs Memento: duas abordagens para implementar undo

Você já travou ao tentar criar uma função de undo por conta própria?

É uma função comum que termina com um único Ctrl+Z, mas levá-la para o código pode ser surpreendentemente trabalhoso. Até em um projeto de brinquedo parecido com o Paint, é fácil passar dias nisso.

Indo direto ao ponto: a implementação de undo segue, em linhas gerais, duas abordagens.

Uma é o padrão Command, que desfaz as ações realizadas; a outra é o padrão Memento, que salva o estado completo anterior a uma ação.

As duas estão corretas. A escolha depende de quando e por que você usa cada uma. Hoje vamos compará-las com código.


Padrão Command — Encapsulando ações em objetos

O padrão Command coloca em um único objeto o que foi feito.

Pense nele como um objeto que combina execute e undo.

Por exemplo, se a ação for inserir texto, você define antecipadamente no mesmo objeto o oposto: apagar exatamente o que foi inserido.

protocol Command {
    func execute()
    func undo()
}

struct TypeCommand: Command {
    let text: String
    let editor: Editor
    func execute() { editor.content += text }
    func undo() { editor.content.removeLast(text.count) }
}

Esses objetos Command são empilhados um a um.

Quando chega um Ctrl+Z, basta retirar o item do topo e chamar undo(). É só isso.

O padrão Command armazena ações, não estado.


Padrão Memento — Salvando o estado como snapshots

O Memento segue uma abordagem completamente diferente.

Em vez de pensar em como desfazer cada ação, você salva o estado inteiro imediatamente antes da mudança, como se tirasse uma foto.

struct Memento { let content: String }

class Editor {
    var content = ""
    func save() -> Memento { Memento(content: content) }
    func restore(_ m: Memento) { content = m.content }
}

let editor = Editor()
editor.content = "Olá"
let snapshot = editor.save()   // Salvar snapshot aqui
editor.content = "Olá"
editor.restore(snapshot)
print(editor.content)
// Saída: Olá

Quando o undo for necessário, basta substituir o estado pelo snapshot salvo. É só isso.

É um alívio não precisar escrever a lógica de reversão para cada ação.

Mas a desvantagem também é clara. Se o estado for grande, ele consome bastante memória, porque é copiado por inteiro a cada vez.

Diagrama da estrutura que empilha objetos Command
Diagrama da estrutura que empilha objetos Command

Então, qual usar?

Organizei em uma tabela as diferenças que percebi depois de usar os dois.

Critério Padrão Command Padrão Memento
O que é armazenado Ação (execute/undo) Snapshot do estado
Memória Leve Pesado quando o estado é grande
Lógica de undo Implementada manualmente para cada ação Basta restaurar
redo Natural ao desempilhar Possível com um array de snapshots
Onde se encaixa melhor Editores, ferramentas gráficas Saves de jogos, estado de formulários

Resumindo em uma linha:

Se você consegue definir claramente a ação inversa, use Command; se for mais simples capturar o estado inteiro, use Memento.

Aquela mesa onde passei dias lutando com um único Ctrl+Z
Aquela mesa onde passei dias lutando com um único Ctrl+Z

Quando usar e quando evitar

Na prática, escolho com base nestes critérios.

  • Os limites das ações são claros e redo também é necessário → Command. Editores e ferramentas de desenho são exemplos típicos.
  • O estado é pequeno e copiar snapshots não pesa → Memento. Por exemplo, entradas de formulários e restauração de configurações.
  • O estado é enorme, mas as mudanças são locais → Evite Memento. Copiar tudo a cada vez pode esgotar a memória. Command é melhor.
  • Você não consegue imaginar a lógica de reversão → Não force o uso de Command; salvar snapshots com Memento é mais limpo.

No trabalho, também é comum combinar os dois. As ações são gerenciadas com Command, enquanto apenas as partes difíceis de desfazer recebem snapshots Memento.


É assim que perguntam em entrevistas

P. Qual é a diferença entre os padrões Command e Memento?

Command encapsula uma ação em um objeto e mantém execute e undo em par. Memento salva o estado de um objeto como snapshot e o restaura depois. Em outras palavras, a diferença está em desfazer pela operação inversa ou pela restauração do estado.

P. Se você implementasse uma pilha de undo, qual escolheria?

Se os limites das ações forem claros e você quiser oferecer redo naturalmente, escolha Command. Se o estado for pequeno e escrever a lógica de reversão der trabalho, empilhar snapshots com Memento é mais simples e seguro.


Recomendo implementar os dois padrões por conta própria pelo menos uma vez. Ler os conceitos e ver os objetos se acumulando na pilha e o estado voltando com Ctrl+Z produz níveis de compreensão completamente diferentes. Espero que este artigo ajude você a dar o primeiro passo.

Continue lendo