Introducao
O Decorator (Decorador) e um padrao estrutural que permite adicionar funcionalidades a um objeto sem alterar sua interface original. Em vez de usar heranca para estender comportamento, o Decorator envolve (wraps) o objeto original e delega chamadas, adicionando logica antes ou depois.
Em Rust, o Decorator e particularmente elegante porque traits permitem composicao flexivel sem heranca. O padrao de middleware amplamente usado em web frameworks como Actix e Axum e essencialmente o Decorator aplicado a handlers HTTP.
Problema
Voce tem um servico que processa requisicoes. Com o tempo, precisa adicionar varias funcionalidades transversais: logging, autenticacao, cache, rate limiting, compressao. Cada funcionalidade deve ser independente e combinavel livremente.
// PROBLEMA: Cada combinacao requer uma implementacao separada
fn processar_com_log_e_cache(req: &Request) -> Response { /* ... */ }
fn processar_com_log_e_auth(req: &Request) -> Response { /* ... */ }
fn processar_com_cache_e_auth(req: &Request) -> Response { /* ... */ }
fn processar_com_log_cache_e_auth(req: &Request) -> Response { /* ... */ }
// Explosao combinatoria! Impossivel de manter.
Solucao em Rust
Decorator com Trait Objects
use std::collections::HashMap;
use std::time::Instant;
/// Requisicao HTTP simplificada
#[derive(Debug, Clone)]
pub struct Request {
pub metodo: String,
pub caminho: String,
pub headers: HashMap<String, String>,
pub corpo: Option<String>,
}
/// Resposta HTTP simplificada
#[derive(Debug, Clone)]
pub struct Response {
pub status: u16,
pub corpo: String,
pub headers: HashMap<String, String>,
}
impl Response {
pub fn ok(corpo: impl Into<String>) -> Self {
Self {
status: 200,
corpo: corpo.into(),
headers: HashMap::new(),
}
}
pub fn erro(status: u16, mensagem: impl Into<String>) -> Self {
Self {
status,
corpo: mensagem.into(),
headers: HashMap::new(),
}
}
}
/// Trait que define um handler de requisicao
pub trait Handler {
fn processar(&self, req: &Request) -> Response;
}
// === Handler Base (o servico real) ===
pub struct ApiHandler;
impl Handler for ApiHandler {
fn processar(&self, req: &Request) -> Response {
match req.caminho.as_str() {
"/usuarios" => Response::ok(r#"[{"nome": "Maria"}, {"nome": "Joao"}]"#),
"/status" => Response::ok(r#"{"status": "ok"}"#),
_ => Response::erro(404, "Rota nao encontrada"),
}
}
}
// === Decorator: Logging ===
pub struct LoggingDecorator {
inner: Box<dyn Handler>,
nivel: String,
}
impl LoggingDecorator {
pub fn new(inner: Box<dyn Handler>) -> Self {
Self {
inner,
nivel: "INFO".to_string(),
}
}
pub fn com_nivel(mut self, nivel: &str) -> Self {
self.nivel = nivel.to_string();
self
}
}
impl Handler for LoggingDecorator {
fn processar(&self, req: &Request) -> Response {
let inicio = Instant::now();
println!(
"[{}] --> {} {}",
self.nivel, req.metodo, req.caminho
);
// Delega para o handler interno
let resposta = self.inner.processar(req);
let duracao = inicio.elapsed();
println!(
"[{}] <-- {} {} ({:?})",
self.nivel, resposta.status, req.caminho, duracao
);
resposta
}
}
// === Decorator: Autenticacao ===
pub struct AuthDecorator {
inner: Box<dyn Handler>,
token_valido: String,
}
impl AuthDecorator {
pub fn new(inner: Box<dyn Handler>, token: impl Into<String>) -> Self {
Self {
inner,
token_valido: token.into(),
}
}
}
impl Handler for AuthDecorator {
fn processar(&self, req: &Request) -> Response {
// Verifica autenticacao ANTES de delegar
match req.headers.get("Authorization") {
Some(token) if token == &format!("Bearer {}", self.token_valido) => {
println!("[AUTH] Token valido - acesso permitido");
self.inner.processar(req)
}
Some(_) => {
println!("[AUTH] Token invalido!");
Response::erro(401, "Token de autenticacao invalido")
}
None => {
println!("[AUTH] Header Authorization ausente!");
Response::erro(401, "Autenticacao necessaria")
}
}
}
}
// === Decorator: Rate Limiting ===
use std::sync::Mutex;
pub struct RateLimitDecorator {
inner: Box<dyn Handler>,
max_requisicoes: u32,
contador: Mutex<u32>,
}
impl RateLimitDecorator {
pub fn new(inner: Box<dyn Handler>, max: u32) -> Self {
Self {
inner,
max_requisicoes: max,
contador: Mutex::new(0),
}
}
}
impl Handler for RateLimitDecorator {
fn processar(&self, req: &Request) -> Response {
let mut contador = self.contador.lock().unwrap();
if *contador >= self.max_requisicoes {
println!("[RATE LIMIT] Limite excedido!");
return Response::erro(429, "Muitas requisicoes. Tente novamente mais tarde.");
}
*contador += 1;
let contagem_atual = *contador;
drop(contador); // Libera o lock antes de processar
println!(
"[RATE LIMIT] Requisicao {}/{}",
contagem_atual, self.max_requisicoes
);
self.inner.processar(req)
}
}
// === Decorator: Cache ===
pub struct CacheDecorator {
inner: Box<dyn Handler>,
cache: Mutex<HashMap<String, Response>>,
}
impl CacheDecorator {
pub fn new(inner: Box<dyn Handler>) -> Self {
Self {
inner,
cache: Mutex::new(HashMap::new()),
}
}
}
impl Handler for CacheDecorator {
fn processar(&self, req: &Request) -> Response {
// So faz cache de GET
if req.metodo != "GET" {
return self.inner.processar(req);
}
let chave = req.caminho.clone();
let cache = self.cache.lock().unwrap();
if let Some(resposta_cache) = cache.get(&chave) {
println!("[CACHE] HIT para {}", chave);
return resposta_cache.clone();
}
drop(cache);
// Cache MISS - processa e armazena
println!("[CACHE] MISS para {}", chave);
let resposta = self.inner.processar(req);
if resposta.status == 200 {
let mut cache = self.cache.lock().unwrap();
cache.insert(chave, resposta.clone());
}
resposta
}
}
fn main() {
// Composicao de decorators: cada um envolve o anterior
// A ordem importa! Externo -> Interno:
// RateLimit -> Cache -> Auth -> Logging -> ApiHandler
let handler: Box<dyn Handler> = Box::new(ApiHandler);
let handler = Box::new(LoggingDecorator::new(handler));
let handler = Box::new(AuthDecorator::new(handler, "meu-token-secreto"));
let handler = Box::new(CacheDecorator::new(handler));
let handler = Box::new(RateLimitDecorator::new(handler, 5));
// Requisicao autenticada
let req_auth = Request {
metodo: "GET".to_string(),
caminho: "/usuarios".to_string(),
headers: HashMap::from([
("Authorization".to_string(), "Bearer meu-token-secreto".to_string()),
]),
corpo: None,
};
println!("=== Primeira requisicao (cache miss) ===");
let resp1 = handler.processar(&req_auth);
println!("Resposta: {} - {}\n", resp1.status, resp1.corpo);
println!("=== Segunda requisicao (cache hit) ===");
let resp2 = handler.processar(&req_auth);
println!("Resposta: {} - {}\n", resp2.status, resp2.corpo);
// Requisicao sem autenticacao
let req_sem_auth = Request {
metodo: "GET".to_string(),
caminho: "/usuarios".to_string(),
headers: HashMap::new(),
corpo: None,
};
println!("=== Requisicao sem auth ===");
let resp3 = handler.processar(&req_sem_auth);
println!("Resposta: {} - {}", resp3.status, resp3.corpo);
}
Diagrama
COMPOSICAO DE DECORATORS:
Request
|
v
+--------------------+
| RateLimitDecorator | Verifica limite de requisicoes
+----+---------------+
|
v
+--------------------+
| CacheDecorator | Verifica/armazena cache
+----+---------------+
|
v
+--------------------+
| AuthDecorator | Verifica token de autenticacao
+----+---------------+
|
v
+--------------------+
| LoggingDecorator | Registra tempo e status
+----+---------------+
|
v
+--------------------+
| ApiHandler | Handler real (logica de negocio)
+--------------------+
|
v
Response (sobe de volta pela pilha de decorators)
TODOS IMPLEMENTAM A MESMA TRAIT:
trait Handler {
fn processar(&self, req: &Request) -> Response;
}
LoggingDecorator: impl Handler ---inner---> dyn Handler
AuthDecorator: impl Handler ---inner---> dyn Handler
CacheDecorator: impl Handler ---inner---> dyn Handler
ApiHandler: impl Handler (handler base, sem inner)
Exemplo do Mundo Real
Decorator para a trait Write da biblioteca padrao, adicionando compressao, buffer e contagem:
use std::io::{self, Write};
/// Decorator que conta bytes escritos
pub struct ContadorWrite<W: Write> {
inner: W,
bytes_escritos: u64,
}
impl<W: Write> ContadorWrite<W> {
pub fn new(inner: W) -> Self {
Self {
inner,
bytes_escritos: 0,
}
}
pub fn bytes_escritos(&self) -> u64 {
self.bytes_escritos
}
/// Consome o decorator e retorna o writer interno
pub fn into_inner(self) -> W {
self.inner
}
}
impl<W: Write> Write for ContadorWrite<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = self.inner.write(buf)?;
self.bytes_escritos += n as u64;
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
/// Decorator que adiciona prefixo a cada linha
pub struct PrefixoWrite<W: Write> {
inner: W,
prefixo: String,
inicio_linha: bool,
}
impl<W: Write> PrefixoWrite<W> {
pub fn new(inner: W, prefixo: impl Into<String>) -> Self {
Self {
inner,
prefixo: prefixo.into(),
inicio_linha: true,
}
}
}
impl<W: Write> Write for PrefixoWrite<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let texto = String::from_utf8_lossy(buf);
let mut total = 0;
for (i, linha) in texto.split('\n').enumerate() {
if i > 0 {
self.inner.write_all(b"\n")?;
total += 1;
self.inicio_linha = true;
}
if !linha.is_empty() {
if self.inicio_linha {
self.inner.write_all(self.prefixo.as_bytes())?;
self.inicio_linha = false;
}
let n = self.inner.write(linha.as_bytes())?;
total += n;
}
}
Ok(total)
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
/// Decorator que transforma tudo para maiusculas
pub struct MaiusculasWrite<W: Write> {
inner: W,
}
impl<W: Write> MaiusculasWrite<W> {
pub fn new(inner: W) -> Self {
Self { inner }
}
}
impl<W: Write> Write for MaiusculasWrite<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let texto = String::from_utf8_lossy(buf);
let maiusculas = texto.to_uppercase();
self.inner.write_all(maiusculas.as_bytes())?;
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.inner.flush()
}
}
/// Trait de extensao para composicao fluente de decorators
pub trait WriteExt: Write + Sized {
fn com_contador(self) -> ContadorWrite<Self> {
ContadorWrite::new(self)
}
fn com_prefixo(self, prefixo: impl Into<String>) -> PrefixoWrite<Self> {
PrefixoWrite::new(self, prefixo)
}
fn em_maiusculas(self) -> MaiusculasWrite<Self> {
MaiusculasWrite::new(self)
}
}
// Implementa para TODOS os tipos que implementam Write
impl<W: Write> WriteExt for W {}
fn main() {
// Composicao fluente de decorators
let buffer = Vec::new();
let mut writer = buffer
.com_contador()
.com_prefixo("[LOG] ")
.em_maiusculas();
writeln!(writer, "Iniciando aplicacao").unwrap();
writeln!(writer, "Conectando ao banco de dados").unwrap();
writeln!(writer, "Servidor pronto na porta 8080").unwrap();
// Saida gerada pelo pipeline de decorators:
println!("=== Saida decorada ===");
// Para demonstrar, usamos stdout diretamente
let mut stdout_writer = io::stdout()
.com_prefixo("[APP] ");
writeln!(stdout_writer, "Mensagem com prefixo").unwrap();
writeln!(stdout_writer, "Outra mensagem").unwrap();
}
Quando Usar
- Funcionalidades transversais (logging, cache, auth) que se aplicam a multiplos handlers
- Combinacao flexivel de comportamentos sem explosao de subclasses
- Middleware em web frameworks, pipelines de processamento
- Extensao de tipos da std (Write, Read, Iterator) com funcionalidade adicional
- Monitoramento e instrumentacao sem alterar o codigo de negocio
Quando NAO Usar
- Muitos decorators aninhados tornam o debug dificil (stack traces confusos)
- Quando a ordem dos decorators importa demais - pode causar bugs sutis
- Performance critica - cada camada adiciona um nivel de indirection
- Funcionalidade simples - nao crie decorator para algo que uma funcao resolve
// SIMPLES DEMAIS para Decorator - use uma funcao:
fn com_log(resultado: String) -> String {
println!("Resultado: {}", resultado);
resultado
}
Variacoes em Rust
1. Decorator com genericos (sem alocacao)
// Genericos evitam Box<dyn Trait> - sem custo de indirection
pub struct Log<H: Handler> {
inner: H,
}
impl<H: Handler> Handler for Log<H> {
fn processar(&self, req: &Request) -> Response {
println!("LOG: {}", req.caminho);
self.inner.processar(req)
}
}
2. Decorator com closures
/// Decorator funcional usando closures
pub fn com_logging(
handler: impl Fn(&Request) -> Response,
) -> impl Fn(&Request) -> Response {
move |req| {
println!("Antes: {}", req.caminho);
let resp = handler(req);
println!("Depois: {}", resp.status);
resp
}
}
3. Decorator via Iterator (padrao da std)
// A std ja usa Decorator extensivamente em iteradores!
let resultado: Vec<i32> = (1..100)
.filter(|n| n % 2 == 0) // FilterDecorator
.map(|n| n * n) // MapDecorator
.take(5) // TakeDecorator
.collect();
// Cada adaptador de iterador e um Decorator!
Padroes Relacionados
- Adapter - Adapter muda a interface; Decorator adiciona funcionalidade mantendo a interface
- Composite - Ambos usam composicao recursiva, mas com propositos diferentes
- Strategy - Strategy troca o algoritmo inteiro; Decorator adiciona camadas ao existente
- Facade - Facade simplifica; Decorator enriquece
Conclusao
O Decorator e um dos padroes mais poderosos e amplamente usados em Rust. A composicao
via traits se encaixa perfeitamente na filosofia de Rust de “composicao sobre heranca”.
Os iteradores da biblioteca padrao sao o exemplo mais evidente: map, filter, take
sao todos decorators que envolvem o iterador anterior. Ao projetar suas APIs com traits
e wrappers, voce cria sistemas modulares, testaveis e extensiveis, onde funcionalidades
podem ser adicionadas ou removidas como camadas de uma cebola, sem alterar o codigo
existente.