Introducao
O Strategy (Estrategia) e um padrao comportamental que permite definir uma familia de algoritmos, encapsular cada um deles e torna-los intercambiaveis. O contexto que usa o algoritmo nao precisa saber qual estrategia concreta esta sendo utilizada.
Em Rust, o Strategy e extraordinariamente versatil porque a linguagem oferece tres formas distintas de implementa-lo, cada uma com diferentes trade-offs de performance, flexibilidade e ergonomia: trait objects, closures e enums.
Problema
Voce esta construindo um sistema de processamento de dados que precisa suportar multiplos algoritmos de compressao. Diferentes clientes possuem diferentes requisitos: alguns priorizam velocidade, outros priorizam taxa de compressao, outros precisam de compatibilidade com formatos especificos.
// Sem Strategy: condicoes espalhadas por todo o codigo
fn comprimir(dados: &[u8], formato: &str) -> Vec<u8> {
match formato {
"gzip" => { /* logica gzip */ todo!() }
"zstd" => { /* logica zstd */ todo!() }
"lz4" => { /* logica lz4 */ todo!() }
"snappy" => { /* logica snappy */ todo!() }
// Adicionar novo formato = modificar ESTA funcao
// E todas as outras funcoes que usam formato...
_ => panic!("formato desconhecido"),
}
}
Solucao em Rust
Abordagem 1: Trait Objects (Despacho Dinamico)
A forma mais flexivel, ideal quando as estrategias sao definidas em tempo de execucao:
/// Trait que define a interface da estrategia
pub trait Compressor: Send + Sync {
/// Comprime os dados de entrada
fn comprimir(&self, dados: &[u8]) -> Vec<u8>;
/// Descomprime os dados
fn descomprimir(&self, dados: &[u8]) -> Vec<u8>;
/// Retorna o nome do algoritmo
fn nome(&self) -> &str;
/// Estima a taxa de compressao (menor = melhor compressao)
fn taxa_estimada(&self) -> f64;
}
/// Estrategia: compressao simulada por run-length encoding
pub struct RleCompressor;
impl Compressor for RleCompressor {
fn comprimir(&self, dados: &[u8]) -> Vec<u8> {
let mut resultado = Vec::new();
let mut i = 0;
while i < dados.len() {
let byte_atual = dados[i];
let mut contagem: u8 = 1;
while i + contagem as usize < dados.len()
&& dados[i + contagem as usize] == byte_atual
&& contagem < 255
{
contagem += 1;
}
resultado.push(contagem);
resultado.push(byte_atual);
i += contagem as usize;
}
resultado
}
fn descomprimir(&self, dados: &[u8]) -> Vec<u8> {
let mut resultado = Vec::new();
for chunk in dados.chunks(2) {
if chunk.len() == 2 {
for _ in 0..chunk[0] {
resultado.push(chunk[1]);
}
}
}
resultado
}
fn nome(&self) -> &str {
"RLE (Run-Length Encoding)"
}
fn taxa_estimada(&self) -> f64 {
0.7 // eficiente para dados repetitivos
}
}
/// Estrategia: compressao por dicionario simples
pub struct DicionarioCompressor {
tamanho_janela: usize,
}
impl DicionarioCompressor {
pub fn new(tamanho_janela: usize) -> Self {
Self { tamanho_janela }
}
}
impl Compressor for DicionarioCompressor {
fn comprimir(&self, dados: &[u8]) -> Vec<u8> {
// Implementacao simplificada para demonstracao
println!(
"[Dicionario] Comprimindo {} bytes com janela de {}",
dados.len(),
self.tamanho_janela
);
// Simulacao: em producao usaria LZ77/LZ78
let mut resultado = dados.to_vec();
resultado.push(0xFF); // marcador
resultado
}
fn descomprimir(&self, dados: &[u8]) -> Vec<u8> {
// Remove o marcador
let mut resultado = dados.to_vec();
if resultado.last() == Some(&0xFF) {
resultado.pop();
}
resultado
}
fn nome(&self) -> &str {
"Dicionario (LZ-like)"
}
fn taxa_estimada(&self) -> f64 {
0.4 // boa taxa de compressao
}
}
/// Estrategia: sem compressao (passthrough)
pub struct SemCompressao;
impl Compressor for SemCompressao {
fn comprimir(&self, dados: &[u8]) -> Vec<u8> {
dados.to_vec()
}
fn descomprimir(&self, dados: &[u8]) -> Vec<u8> {
dados.to_vec()
}
fn nome(&self) -> &str {
"Nenhuma (passthrough)"
}
fn taxa_estimada(&self) -> f64 {
1.0 // sem compressao
}
}
/// Contexto que usa a estrategia de compressao
pub struct ProcessadorArquivos {
compressor: Box<dyn Compressor>,
nome: String,
}
impl ProcessadorArquivos {
pub fn new(nome: &str, compressor: Box<dyn Compressor>) -> Self {
Self {
compressor,
nome: nome.to_string(),
}
}
/// Troca a estrategia em tempo de execucao
pub fn trocar_compressor(&mut self, compressor: Box<dyn Compressor>) {
println!(
"[{}] Trocando compressor: {} -> {}",
self.nome,
self.compressor.nome(),
compressor.nome()
);
self.compressor = compressor;
}
/// Processa dados usando a estrategia atual
pub fn processar(&self, dados: &[u8]) -> Vec<u8> {
println!(
"[{}] Processando {} bytes com '{}'",
self.nome,
dados.len(),
self.compressor.nome()
);
let comprimido = self.compressor.comprimir(dados);
let taxa = comprimido.len() as f64 / dados.len() as f64;
println!(
"[{}] Resultado: {} -> {} bytes (taxa: {:.1}%)",
self.nome,
dados.len(),
comprimido.len(),
taxa * 100.0
);
comprimido
}
}
fn main() {
// Selecao de estrategia em tempo de execucao
let dados = b"AAAAAABBBBCCCCCCCCDDDDDDDDDDDD";
let mut proc = ProcessadorArquivos::new("Servidor", Box::new(RleCompressor));
let comprimido = proc.processar(dados);
// Verifica integridade
let descomprimido = RleCompressor.descomprimir(&comprimido);
assert_eq!(dados.as_slice(), descomprimido.as_slice());
println!("Integridade verificada!\n");
// Troca estrategia em tempo de execucao
proc.trocar_compressor(Box::new(DicionarioCompressor::new(4096)));
proc.processar(dados);
}
Abordagem 2: Closures (Funcional)
Ideal para estrategias simples e composicao funcional:
/// Tipo para funcao de ordenacao customizada
type ComparadorFn<T> = Box<dyn Fn(&T, &T) -> std::cmp::Ordering>;
#[derive(Debug, Clone)]
pub struct Produto {
pub nome: String,
pub preco: f64,
pub avaliacao: f64,
pub vendas: u64,
}
/// Fabrica de estrategias de ordenacao usando closures
pub mod estrategias_ordenacao {
use super::*;
pub fn por_preco_crescente() -> ComparadorFn<Produto> {
Box::new(|a, b| {
a.preco
.partial_cmp(&b.preco)
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn por_preco_decrescente() -> ComparadorFn<Produto> {
Box::new(|a, b| {
b.preco
.partial_cmp(&a.preco)
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn por_avaliacao() -> ComparadorFn<Produto> {
Box::new(|a, b| {
b.avaliacao
.partial_cmp(&a.avaliacao)
.unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn por_popularidade() -> ComparadorFn<Produto> {
Box::new(|a, b| b.vendas.cmp(&a.vendas))
}
/// Estrategia composta: ordena por multiplos criterios
pub fn multi_criterio(
criterios: Vec<ComparadorFn<Produto>>,
) -> ComparadorFn<Produto> {
Box::new(move |a, b| {
for criterio in &criterios {
let resultado = criterio(a, b);
if resultado != std::cmp::Ordering::Equal {
return resultado;
}
}
std::cmp::Ordering::Equal
})
}
}
fn demonstrar_closures() {
let mut produtos = vec![
Produto { nome: "Notebook".into(), preco: 4500.0, avaliacao: 4.5, vendas: 120 },
Produto { nome: "Mouse".into(), preco: 89.90, avaliacao: 4.8, vendas: 5000 },
Produto { nome: "Teclado".into(), preco: 250.0, avaliacao: 4.2, vendas: 800 },
Produto { nome: "Monitor".into(), preco: 1800.0, avaliacao: 4.6, vendas: 300 },
Produto { nome: "Fone".into(), preco: 350.0, avaliacao: 4.8, vendas: 2000 },
];
// Estrategia 1: por preco
let comparador = estrategias_ordenacao::por_preco_crescente();
produtos.sort_by(|a, b| comparador(a, b));
println!("=== Por preco (crescente) ===");
for p in &produtos {
println!(" {} - R${:.2}", p.nome, p.preco);
}
// Estrategia 2: por avaliacao
let comparador = estrategias_ordenacao::por_avaliacao();
produtos.sort_by(|a, b| comparador(a, b));
println!("\n=== Por avaliacao (melhor primeiro) ===");
for p in &produtos {
println!(" {} - {:.1} estrelas", p.nome, p.avaliacao);
}
// Estrategia 3: multi-criterio (avaliacao, depois vendas)
let comparador = estrategias_ordenacao::multi_criterio(vec![
estrategias_ordenacao::por_avaliacao(),
estrategias_ordenacao::por_popularidade(),
]);
produtos.sort_by(|a, b| comparador(a, b));
println!("\n=== Por avaliacao + popularidade ===");
for p in &produtos {
println!(
" {} - {:.1} estrelas, {} vendas",
p.nome, p.avaliacao, p.vendas
);
}
}
Abordagem 3: Enums (Despacho Estatico)
Maximo desempenho quando as estrategias sao conhecidas em tempo de compilacao:
/// Estrategia de calculo de frete como enum
#[derive(Debug, Clone)]
pub enum EstrategiaFrete {
/// Frete fixo por regiao
Fixo { valor: f64 },
/// Frete calculado por peso
PorPeso { preco_por_kg: f64, minimo: f64 },
/// Frete gratis acima de um valor
GratisAcimaDe { limite: f64, frete_padrao: f64 },
/// Frete expresso (percentual do pedido)
Expresso { percentual: f64, minimo: f64 },
}
impl EstrategiaFrete {
/// Calcula o valor do frete - despacho estatico via match
pub fn calcular(&self, valor_pedido: f64, peso_kg: f64) -> f64 {
match self {
Self::Fixo { valor } => *valor,
Self::PorPeso { preco_por_kg, minimo } => {
let calculado = peso_kg * preco_por_kg;
calculado.max(*minimo)
}
Self::GratisAcimaDe { limite, frete_padrao } => {
if valor_pedido >= *limite {
0.0
} else {
*frete_padrao
}
}
Self::Expresso { percentual, minimo } => {
let calculado = valor_pedido * percentual / 100.0;
calculado.max(*minimo)
}
}
}
pub fn descricao(&self) -> String {
match self {
Self::Fixo { valor } => format!("Fixo: R${:.2}", valor),
Self::PorPeso { preco_por_kg, .. } => {
format!("Por peso: R${:.2}/kg", preco_por_kg)
}
Self::GratisAcimaDe { limite, .. } => {
format!("Gratis acima de R${:.2}", limite)
}
Self::Expresso { percentual, .. } => {
format!("Expresso: {}% do pedido", percentual)
}
}
}
}
fn demonstrar_enums() {
let estrategias = vec![
EstrategiaFrete::Fixo { valor: 15.90 },
EstrategiaFrete::PorPeso { preco_por_kg: 5.0, minimo: 10.0 },
EstrategiaFrete::GratisAcimaDe { limite: 200.0, frete_padrao: 25.0 },
EstrategiaFrete::Expresso { percentual: 5.0, minimo: 20.0 },
];
let valor_pedido = 350.0;
let peso = 2.5;
println!("Pedido: R${:.2}, Peso: {:.1}kg\n", valor_pedido, peso);
for estrategia in &estrategias {
let frete = estrategia.calcular(valor_pedido, peso);
println!(
" {}: R${:.2}",
estrategia.descricao(),
frete
);
}
}
Diagrama
STRATEGY COM TRAIT OBJECTS:
+---------------------+ +------------------+
| ProcessadorArquivos |-------->| dyn Compressor |
| | +------------------+
| compressor: Box< | ^ ^ ^
| dyn Compressor> | | | |
+---------------------+ | | |
| | |
+--------------------------+ | +------------------+
| | |
+---------+--------+ +---------+--------+ +--------+-------+
| RleCompressor | | DicionarioCompr | | SemCompressao |
+------------------+ +------------------+ +----------------+
STRATEGY COM CLOSURES:
sort_by(|a, b| comparador(a, b))
|
v
+------------------+
| Box<dyn Fn( |
| &T, &T |
| ) -> Ordering> |
+------------------+
Qualquer closure serve!
STRATEGY COM ENUM:
match self {
Fixo { .. } => ..., <-- despacho em tempo
PorPeso { .. } => ..., de compilacao
Expresso { .. } => ..., (sem vtable)
}
Exemplo do Mundo Real
Sistema de validacao com estrategias combinaveis:
/// Resultado de uma validacao
#[derive(Debug)]
pub struct ErroValidacao {
pub campo: String,
pub mensagem: String,
}
/// Estrategia de validacao para campos de formulario
pub trait Validador: Send + Sync {
fn validar(&self, campo: &str, valor: &str) -> Result<(), ErroValidacao>;
fn descricao(&self) -> &str;
}
pub struct NaoVazio;
impl Validador for NaoVazio {
fn validar(&self, campo: &str, valor: &str) -> Result<(), ErroValidacao> {
if valor.trim().is_empty() {
Err(ErroValidacao {
campo: campo.to_string(),
mensagem: "Campo nao pode ser vazio".to_string(),
})
} else {
Ok(())
}
}
fn descricao(&self) -> &str { "nao vazio" }
}
pub struct TamanhoMinimo(pub usize);
impl Validador for TamanhoMinimo {
fn validar(&self, campo: &str, valor: &str) -> Result<(), ErroValidacao> {
if valor.len() < self.0 {
Err(ErroValidacao {
campo: campo.to_string(),
mensagem: format!("Minimo de {} caracteres (tem {})", self.0, valor.len()),
})
} else {
Ok(())
}
}
fn descricao(&self) -> &str { "tamanho minimo" }
}
pub struct EmailValido;
impl Validador for EmailValido {
fn validar(&self, campo: &str, valor: &str) -> Result<(), ErroValidacao> {
if !valor.contains('@') || !valor.contains('.') {
Err(ErroValidacao {
campo: campo.to_string(),
mensagem: "Email invalido".to_string(),
})
} else {
Ok(())
}
}
fn descricao(&self) -> &str { "email valido" }
}
/// Combinador de validadores (Strategy + Composite)
pub struct ValidadorComposto {
validadores: Vec<Box<dyn Validador>>,
}
impl ValidadorComposto {
pub fn new() -> Self {
Self { validadores: Vec::new() }
}
pub fn adicionar(mut self, v: Box<dyn Validador>) -> Self {
self.validadores.push(v);
self
}
pub fn validar_campo(&self, campo: &str, valor: &str) -> Vec<ErroValidacao> {
self.validadores
.iter()
.filter_map(|v| v.validar(campo, valor).err())
.collect()
}
}
fn main() {
// Compoe estrategias de validacao
let validador_email = ValidadorComposto::new()
.adicionar(Box::new(NaoVazio))
.adicionar(Box::new(TamanhoMinimo(5)))
.adicionar(Box::new(EmailValido));
let validador_senha = ValidadorComposto::new()
.adicionar(Box::new(NaoVazio))
.adicionar(Box::new(TamanhoMinimo(8)));
// Testa com valores diferentes
let testes = vec![
("email", "", &validador_email),
("email", "ab", &validador_email),
("email", "usuario@email.com", &validador_email),
("senha", "123", &validador_senha),
("senha", "senhaforte123", &validador_senha),
];
for (campo, valor, validador) in testes {
let erros = validador.validar_campo(campo, valor);
if erros.is_empty() {
println!("{} = '{}' -> OK", campo, valor);
} else {
println!("{} = '{}' -> ERROS:", campo, valor);
for e in erros {
println!(" - {}", e.mensagem);
}
}
}
}
Quando Usar
- Multiplos algoritmos intercambiaveis para a mesma tarefa
- Selecao de algoritmo em tempo de execucao (configuracao, input do usuario)
- Eliminar condicionais extensos (substituir match/if-else por polimorfismo)
- Testes - facilita trocar implementacao real por mock
- Combinacao de comportamentos (validacao, filtros, pipelines)
Quando NAO Usar
- Apenas 2-3 variantes simples - um
matchdireto e mais claro - O algoritmo nunca muda em tempo de execucao - use genericos com traits
- Overhead de trait objects importa - prefira enums ou genericos
Variacoes em Rust
1. Strategy com genericos (custo zero)
// Despacho estatico - sem vtable, inlining possivel
pub struct Processador<C: Compressor> {
compressor: C,
}
impl<C: Compressor> Processador<C> {
pub fn processar(&self, dados: &[u8]) -> Vec<u8> {
self.compressor.comprimir(dados)
}
}
// O tipo concreto e fixo em tempo de compilacao
2. Strategy com fn pointer
// Para estrategias sem estado, fn pointers sao mais leves que Box<dyn Fn>
type Formatador = fn(f64) -> String;
fn real_brasileiro(valor: f64) -> String {
format!("R${:.2}", valor)
}
fn dolar(valor: f64) -> String {
format!("${:.2}", valor)
}
struct Relatorio {
formatador: Formatador,
}
3. Strategy com enum + dados
// Combina a performance do enum com flexibilidade de dados associados
enum Desconto {
Percentual(f64),
Fixo(f64),
CompraMinima { minimo: f64, desconto_pct: f64 },
}
impl Desconto {
fn aplicar(&self, valor: f64) -> f64 {
match self {
Self::Percentual(pct) => valor * (1.0 - pct / 100.0),
Self::Fixo(desc) => (valor - desc).max(0.0),
Self::CompraMinima { minimo, desconto_pct } => {
if valor >= *minimo {
valor * (1.0 - desconto_pct / 100.0)
} else {
valor
}
}
}
}
}
Padroes Relacionados
- Factory - Factory pode criar a estrategia adequada
- Decorator - Decorator empilha comportamento; Strategy troca comportamento
- Observer - Observer notifica sobre mudancas; Strategy muda o como algo e feito
- Composite - Strategies podem ser compostas em arvore (validadores compostos)
Conclusao
O Strategy em Rust oferece uma riqueza unica de implementacoes. Trait objects fornecem flexibilidade maxima com despacho dinamico. Closures permitem estrategias ad-hoc sem definir novos tipos. Enums oferecem performance maxima com despacho estatico. E genericos com trait bounds dao custo zero com monomorfizacao. A escolha entre essas abordagens depende do cenario: se a estrategia e conhecida em tempo de compilacao, prefira genericos ou enums; se precisa trocar em tempo de execucao, use trait objects. Essa versatilidade torna o Strategy um dos padroes mais poderosos e idiomaticos em Rust.