Command Pattern em Rust: Encapsulando Ações com Enums e Trait Objects

Implementação completa do padrão Command em Rust com enums, trait objects, undo/redo, gravação de macros e exemplos práticos de editor de texto.

O Command Pattern (Padrão de Comando) é um dos padrões comportamentais mais versáteis do catálogo GoF. Ele encapsula uma solicitação como um objeto, permitindo parametrizar clientes com diferentes solicitações, enfileirar ou registrar solicitações e suportar operações reversíveis (undo/redo).

Em Rust, o Command Pattern ganha vida de duas formas muito idiomáticas: usando enums para representar comandos como variantes de dados, ou usando trait objects (Box<dyn Command>) para polimorfismo dinâmico. Ambas as abordagens se beneficiam enormemente do sistema de ownership e do pattern matching exaustivo do Rust.

Problema

Imagine que você está construindo um editor de texto. O usuário precisa poder digitar, apagar, mover o cursor — e mais importante, precisa de undo e redo. Sem o Command Pattern, cada operação ficaria acoplada diretamente à interface gráfica, tornando impossível:

  • Desfazer e refazer ações de forma uniforme
  • Gravar macros (sequências de comandos para replay)
  • Serializar comandos para persistência ou rede
  • Testar operações isoladamente da UI

O código vira um emaranhado de if/else e estado mutável espalhado por toda parte.

Solução em Rust

Abordagem 1: Comandos como Enum

A forma mais idiomática em Rust é usar enums para representar cada variante de comando:

use std::fmt;

/// Representa todas as operações possíveis no editor
#[derive(Debug, Clone)]
enum ComandoEditor {
    /// Insere texto na posição atual do cursor
    InserirTexto { posicao: usize, texto: String },
    /// Remove texto a partir de uma posição
    RemoverTexto { posicao: usize, texto_removido: String },
    /// Move o cursor para uma nova posição
    MoverCursor { posicao_anterior: usize, nova_posicao: usize },
    /// Substitui texto encontrado por outro
    Substituir {
        posicao: usize,
        texto_antigo: String,
        texto_novo: String,
    },
}

/// O documento sendo editado
#[derive(Debug)]
struct Documento {
    conteudo: String,
    posicao_cursor: usize,
}

impl Documento {
    fn novo() -> Self {
        Documento {
            conteudo: String::new(),
            posicao_cursor: 0,
        }
    }

    /// Executa um comando e retorna se foi bem-sucedido
    fn executar(&mut self, comando: &ComandoEditor) -> bool {
        match comando {
            ComandoEditor::InserirTexto { posicao, texto } => {
                if *posicao <= self.conteudo.len() {
                    self.conteudo.insert_str(*posicao, texto);
                    self.posicao_cursor = posicao + texto.len();
                    true
                } else {
                    false
                }
            }
            ComandoEditor::RemoverTexto { posicao, texto_removido } => {
                let fim = posicao + texto_removido.len();
                if fim <= self.conteudo.len() {
                    self.conteudo.drain(*posicao..fim);
                    self.posicao_cursor = *posicao;
                    true
                } else {
                    false
                }
            }
            ComandoEditor::MoverCursor { nova_posicao, .. } => {
                if *nova_posicao <= self.conteudo.len() {
                    self.posicao_cursor = *nova_posicao;
                    true
                } else {
                    false
                }
            }
            ComandoEditor::Substituir {
                posicao,
                texto_antigo,
                texto_novo,
            } => {
                let fim = posicao + texto_antigo.len();
                if fim <= self.conteudo.len() {
                    self.conteudo.drain(*posicao..fim);
                    self.conteudo.insert_str(*posicao, texto_novo);
                    self.posicao_cursor = posicao + texto_novo.len();
                    true
                } else {
                    false
                }
            }
        }
    }

    /// Desfaz um comando (operação inversa)
    fn desfazer(&mut self, comando: &ComandoEditor) {
        match comando {
            ComandoEditor::InserirTexto { posicao, texto } => {
                // Desfazer inserção = remover o texto inserido
                let fim = posicao + texto.len();
                self.conteudo.drain(*posicao..fim);
                self.posicao_cursor = *posicao;
            }
            ComandoEditor::RemoverTexto { posicao, texto_removido } => {
                // Desfazer remoção = reinserir o texto removido
                self.conteudo.insert_str(*posicao, texto_removido);
                self.posicao_cursor = posicao + texto_removido.len();
            }
            ComandoEditor::MoverCursor { posicao_anterior, .. } => {
                self.posicao_cursor = *posicao_anterior;
            }
            ComandoEditor::Substituir {
                posicao,
                texto_antigo,
                texto_novo,
            } => {
                let fim = posicao + texto_novo.len();
                self.conteudo.drain(*posicao..fim);
                self.conteudo.insert_str(*posicao, texto_antigo);
                self.posicao_cursor = posicao + texto_antigo.len();
            }
        }
    }
}

/// Gerenciador de histórico com suporte a undo/redo
struct GerenciadorHistorico {
    documento: Documento,
    historico: Vec<ComandoEditor>,
    pilha_redo: Vec<ComandoEditor>,
}

impl GerenciadorHistorico {
    fn novo() -> Self {
        GerenciadorHistorico {
            documento: Documento::novo(),
            historico: Vec::new(),
            pilha_redo: Vec::new(),
        }
    }

    /// Executa um comando e o adiciona ao histórico
    fn executar(&mut self, comando: ComandoEditor) -> bool {
        if self.documento.executar(&comando) {
            self.historico.push(comando);
            // Limpa a pilha de redo ao executar novo comando
            self.pilha_redo.clear();
            true
        } else {
            false
        }
    }

    /// Desfaz o último comando
    fn desfazer(&mut self) -> Option<()> {
        let comando = self.historico.pop()?;
        self.documento.desfazer(&comando);
        self.pilha_redo.push(comando);
        Some(())
    }

    /// Refaz o último comando desfeito
    fn refazer(&mut self) -> Option<()> {
        let comando = self.pilha_redo.pop()?;
        self.documento.executar(&comando);
        self.historico.push(comando);
        Some(())
    }

    /// Retorna o conteúdo atual do documento
    fn conteudo(&self) -> &str {
        &self.documento.conteudo
    }

    /// Retorna a posição do cursor
    fn cursor(&self) -> usize {
        self.documento.posicao_cursor
    }
}

fn main() {
    let mut editor = GerenciadorHistorico::novo();

    // Inserindo texto
    editor.executar(ComandoEditor::InserirTexto {
        posicao: 0,
        texto: "Olá, mundo!".to_string(),
    });
    println!("Após inserir: '{}'", editor.conteudo());

    // Substituindo texto
    editor.executar(ComandoEditor::Substituir {
        posicao: 5,
        texto_antigo: "mundo".to_string(),
        texto_novo: "Rust".to_string(),
    });
    println!("Após substituir: '{}'", editor.conteudo());

    // Desfazendo
    editor.desfazer();
    println!("Após undo: '{}'", editor.conteudo());

    // Refazendo
    editor.refazer();
    println!("Após redo: '{}'", editor.conteudo());
}

Abordagem 2: Comandos como Trait Objects

Quando o sistema precisa ser extensível (plugins, por exemplo), usamos traits:

/// Trait que define a interface de um comando
trait Comando: fmt::Debug {
    /// Executa o comando no documento
    fn executar(&self, doc: &mut Documento) -> bool;
    /// Desfaz o comando
    fn desfazer(&self, doc: &mut Documento);
    /// Cria uma cópia do comando para o histórico
    fn clonar(&self) -> Box<dyn Comando>;
    /// Descrição legível do comando
    fn descricao(&self) -> String;
}

#[derive(Debug, Clone)]
struct ComandoInserir {
    posicao: usize,
    texto: String,
}

impl Comando for ComandoInserir {
    fn executar(&self, doc: &mut Documento) -> bool {
        if self.posicao <= doc.conteudo.len() {
            doc.conteudo.insert_str(self.posicao, &self.texto);
            doc.posicao_cursor = self.posicao + self.texto.len();
            true
        } else {
            false
        }
    }

    fn desfazer(&self, doc: &mut Documento) {
        let fim = self.posicao + self.texto.len();
        doc.conteudo.drain(self.posicao..fim);
        doc.posicao_cursor = self.posicao;
    }

    fn clonar(&self) -> Box<dyn Comando> {
        Box::new(self.clone())
    }

    fn descricao(&self) -> String {
        format!("Inserir '{}' na posição {}", self.texto, self.posicao)
    }
}

/// Editor que aceita qualquer comando via trait object
struct EditorExtensivel {
    documento: Documento,
    historico: Vec<Box<dyn Comando>>,
}

Diagrama

                    ┌─────────────────────────────┐
                    │     GerenciadorHistorico     │
                    │─────────────────────────────│
                    │ - historico: Vec<Comando>    │
                    │ - pilha_redo: Vec<Comando>   │
                    │─────────────────────────────│
                    │ + executar(cmd)              │
                    │ + desfazer()                 │
                    │ + refazer()                  │
                    └──────────┬──────────────────┘
                               │ possui
                               ▼
                    ┌─────────────────────────────┐
                    │       ComandoEditor          │
                    │       (enum)                 │
                    │─────────────────────────────│
                    │ ◆ InserirTexto              │
                    │ ◆ RemoverTexto              │
                    │ ◆ MoverCursor               │
                    │ ◆ Substituir                │
                    └──────────┬──────────────────┘
                               │ opera sobre
                               ▼
                    ┌─────────────────────────────┐
                    │        Documento             │
                    │─────────────────────────────│
                    │ - conteudo: String           │
                    │ - posicao_cursor: usize      │
                    └─────────────────────────────┘

Fluxo de Undo/Redo:

  executar()         desfazer()          refazer()
  ┌──────┐          ┌──────┐           ┌──────┐
  │ cmd  │──push──▶│hist. │──pop────▶│redo  │──pop──┐
  └──────┘          │stack │◀──push──│stack │       │
                    └──────┘          └──────┘       │
                                                      │
                        ┌─────────────────────────────┘
                        ▼
                   doc.executar(cmd)

Exemplo do Mundo Real

Um editor de texto completo com suporte a macros (gravação e replay de sequências de comandos):

use std::collections::HashMap;

/// Sistema de macros: grava e reproduz sequências de comandos
#[derive(Debug)]
struct SistemaMacros {
    gravando: bool,
    macro_atual: Vec<ComandoEditor>,
    macros_salvas: HashMap<String, Vec<ComandoEditor>>,
}

impl SistemaMacros {
    fn novo() -> Self {
        SistemaMacros {
            gravando: false,
            macro_atual: Vec::new(),
            macros_salvas: HashMap::new(),
        }
    }

    /// Inicia a gravação de uma macro
    fn iniciar_gravacao(&mut self) {
        self.gravando = true;
        self.macro_atual.clear();
        println!("[MACRO] Gravação iniciada...");
    }

    /// Para a gravação e salva com um nome
    fn parar_gravacao(&mut self, nome: &str) {
        self.gravando = false;
        let comandos = std::mem::take(&mut self.macro_atual);
        let qtd = comandos.len();
        self.macros_salvas.insert(nome.to_string(), comandos);
        println!("[MACRO] '{}' salva com {} comandos", nome, qtd);
    }

    /// Registra um comando durante a gravação
    fn registrar(&mut self, comando: &ComandoEditor) {
        if self.gravando {
            self.macro_atual.push(comando.clone());
        }
    }

    /// Reproduz uma macro salva
    fn reproduzir(&self, nome: &str) -> Option<Vec<ComandoEditor>> {
        self.macros_salvas.get(nome).cloned()
    }
}

/// Editor completo com undo/redo e macros
struct EditorCompleto {
    gerenciador: GerenciadorHistorico,
    macros: SistemaMacros,
}

impl EditorCompleto {
    fn novo() -> Self {
        EditorCompleto {
            gerenciador: GerenciadorHistorico::novo(),
            macros: SistemaMacros::novo(),
        }
    }

    fn executar(&mut self, comando: ComandoEditor) -> bool {
        self.macros.registrar(&comando);
        self.gerenciador.executar(comando)
    }

    fn desfazer(&mut self) -> Option<()> {
        self.gerenciador.desfazer()
    }

    fn refazer(&mut self) -> Option<()> {
        self.gerenciador.refazer()
    }

    fn iniciar_macro(&mut self) {
        self.macros.iniciar_gravacao();
    }

    fn salvar_macro(&mut self, nome: &str) {
        self.macros.parar_gravacao(nome);
    }

    fn executar_macro(&mut self, nome: &str) -> bool {
        if let Some(comandos) = self.macros.reproduzir(nome) {
            for cmd in comandos {
                if !self.gerenciador.executar(cmd) {
                    return false;
                }
            }
            true
        } else {
            println!("Macro '{}' não encontrada", nome);
            false
        }
    }

    fn conteudo(&self) -> &str {
        self.gerenciador.conteudo()
    }
}

fn main() {
    let mut editor = EditorCompleto::novo();

    // Gravando uma macro de formatação
    editor.iniciar_macro();

    editor.executar(ComandoEditor::InserirTexto {
        posicao: 0,
        texto: "# ".to_string(),
    });
    editor.executar(ComandoEditor::InserirTexto {
        posicao: 2,
        texto: "Título do Documento".to_string(),
    });
    editor.executar(ComandoEditor::InserirTexto {
        posicao: 21,
        texto: "\n\n".to_string(),
    });

    editor.salvar_macro("cabecalho");
    println!("Conteúdo: '{}'", editor.conteudo());

    // Desfazendo tudo
    editor.desfazer();
    editor.desfazer();
    editor.desfazer();
    println!("Após 3x undo: '{}'", editor.conteudo());

    // Reproduzindo a macro
    editor.executar_macro("cabecalho");
    println!("Após replay da macro: '{}'", editor.conteudo());
}

Quando Usar

O Command Pattern é recomendado quando:

  • Undo/Redo: Qualquer aplicação que precise desfazer e refazer operações
  • Filas de tarefas: Sistemas que enfileiram operações para execução posterior
  • Macros: Gravação e reprodução de sequências de ações
  • Transações: Agrupamento de operações atômicas com rollback
  • Log de auditoria: Registro de todas as ações realizadas no sistema
  • Sistemas distribuídos: Serialização de comandos para envio via rede

Quando NÃO Usar

Evite o Command Pattern quando:

  • Operações simples: Se não precisa de undo/redo, uma chamada de função direta é mais simples
  • Comandos sem estado: Se o comando não carrega dados, closures ou funções simples são preferíveis
  • Alta performance: A indireção de trait objects pode causar overhead em hot loops (prefira enums)
  • Poucos comandos: Se há apenas 2-3 operações, o padrão adiciona complexidade desnecessária

Variações em Rust

Comandos com Closures

Para comandos simples, closures oferecem uma alternativa leve:

type ComandoFn = Box<dyn Fn(&mut String)>;
type DesfazerFn = Box<dyn Fn(&mut String)>;

struct ComandoClosure {
    executar: ComandoFn,
    desfazer: DesfazerFn,
    descricao: String,
}

impl ComandoClosure {
    fn novo(
        desc: &str,
        exec: impl Fn(&mut String) + 'static,
        desf: impl Fn(&mut String) + 'static,
    ) -> Self {
        ComandoClosure {
            executar: Box::new(exec),
            desfazer: Box::new(desf),
            descricao: desc.to_string(),
        }
    }
}

fn main() {
    let mut texto = String::from("Olá");

    let cmd = ComandoClosure::novo(
        "Adicionar ', mundo!'",
        |t| t.push_str(", mundo!"),
        |t| { t.truncate(3); },
    );

    (cmd.executar)(&mut texto);
    println!("Após executar: {}", texto); // "Olá, mundo!"

    (cmd.desfazer)(&mut texto);
    println!("Após desfazer: {}", texto); // "Olá"
}

Comandos com Async

Para sistemas assíncronos, o padrão se adapta com async_trait:

use std::future::Future;
use std::pin::Pin;

/// Comando assíncrono para operações de I/O
trait ComandoAsync {
    fn executar(&self) -> Pin<Box<dyn Future<Output = Result<(), String>>>>;
    fn descricao(&self) -> &str;
}

struct ComandoSalvarArquivo {
    caminho: String,
    conteudo: String,
}

// A implementação real usaria tokio::fs
// Aqui demonstramos apenas a estrutura do padrão

Padrões Relacionados

  • Memento: Enquanto Command armazena operações, Memento armazena snapshots completos do estado
  • Strategy: Strategy encapsula algoritmos, Command encapsula requisições com dados
  • Observer: Comandos podem ser despachados via sistema de eventos
  • Chain of Responsibility: Comandos podem ser passados por uma cadeia de handlers

Conclusão

O Command Pattern em Rust se beneficia enormemente do sistema de tipos. Enums oferecem a abordagem mais idiomática quando o conjunto de comandos é conhecido em tempo de compilação — o match exaustivo garante que todo comando seja tratado. Trait objects oferecem extensibilidade quando plugins ou módulos externos precisam definir novos comandos.

A combinação de ownership (cada comando possui seus dados), Clone (para cópias no histórico) e pattern matching faz de Rust uma linguagem excepcionalmente adequada para implementar sistemas baseados em comandos, desde editores de texto até sistemas de transações distribuídas.