Introdução: Por que SOLID é importante para desenvolvedores Rust
SOLID são cinco princípios fundamentais de design orientado a objetos que ajudam a criar código flexível, sustentável e escalável. Apesar de Rust não ser uma linguagem OOP clássica, esses princípios são perfeitamente aplicáveis graças ao sistema de traits, ownership e generics. Neste artigo, vamos analisar cada princípio SOLID com exemplos práticos em Rust.
Você aprenderá como:
- Evitar estruturas "divinas" através da decomposição
- Projetar sistemas extensíveis usando traits
- Usar polimorfismo eficientemente sem herança
- Separar interfaces e injetar dependências
1. Single Responsibility Principle (SRP) — Princípio da Responsabilidade Única
O SRP afirma: "Uma estrutura ou módulo deve ter apenas um motivo para mudar". Em Rust, isso significa que cada tipo deve ser responsável por uma tarefa claramente definida.
Violação do SRP: Estrutura "Divina"
struct Report { data: Vec<String>,}
impl Report { fn generate(&self) -> String { self.data.join(\", \") }
fn save_to_file(&self, path: &str) -> std::io::Result<()> { let content = self.generate(); std::fs::write(path, content) }
fn send_email(&self, recipient: &str) { // Lógica de envio de email println!(\"Enviando relatório {} para {}\", self.generate(), recipient); }}Este código viola o SRP: a estrutura Report é responsável pela geração de dados, salvamento em arquivo e envio de email. Alterar a lógica de salvamento ou envio exigiria modificar o próprio relatório.
Implementação correta com SRP
struct Report { data: Vec<String>,}
impl Report { fn new(data: Vec<String>) -> Self { Self { data } }
fn generate(&self) -> String { self.data.join(\", \") }}
struct FileSaver;
impl FileSaver { fn save(report: &Report, path: &str) -> std::io::Result<()> { std::fs::write(path, report.generate()) }}
struct EmailSender;
impl EmailSender { fn send(report: &Report, recipient: &str) { println!(\"Enviando relatório {} para {}\", report.generate(), recipient); }}Agora cada estrutura tem uma única responsabilidade: Report — geração, FileSaver — salvamento, EmailSender — envio. Isso simplifica os testes e a modificação do código.
2. Open/Closed Principle (OCP) — Princípio Aberto/Fechado
O OCP afirma: "As classes devem estar abertas para extensão, mas fechadas para modificação". Em Rust, isso é alcançado através de traits e generics.
Exemplo: Sistema de processamento de pagamentos
trait PaymentProcessor { fn process(&self, amount: f64) -> String;}
struct CreditCardProcessor;
impl PaymentProcessor for CreditCardProcessor { fn process(&self, amount: f64) -> String { format!(\"Processando {} via cartão de crédito\", amount) }}
struct PayPalProcessor;
impl PaymentProcessor for PayPalProcessor { fn process(&self, amount: f64) -> String { format!(\"Processando {} via PayPal\", amount) }}
struct PaymentHandler<T: PaymentProcessor> { processor: T,}
impl<T: PaymentProcessor> PaymentHandler<T> { fn execute(&self, amount: f64) -> String { self.processor.process(amount) }}Adicionar um novo método de pagamento (por exemplo, CryptoProcessor) não requer alteração do código existente — basta implementar o trait PaymentProcessor.