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.