Introducción: Por qué SOLID es importante para los desarrolladores de Rust
SOLID son cinco principios fundamentales de diseño orientado a objetos que ayudan a crear código flexible, mantenible y escalable. A pesar de que Rust no es un lenguaje OOP clásico, estos principios son perfectamente aplicables gracias al sistema de traits, ownership y genéricos. En este artículo analizaremos cada principio SOLID con ejemplos prácticos en Rust.
Aprenderás cómo:
- Evitar estructuras "todopoderosas" mediante la descomposición
- Diseñar sistemas extensibles a través de traits
- Usar polimorfismo eficientemente sin herencia
- Separar interfaces e inyectar dependencias
1. Single Responsibility Principle (SRP) — Principio de Responsabilidad Única
SRP establece: "Una estructura o módulo debe tener solo una razón para cambiar". En Rust, esto significa que cada tipo debe encargarse de una tarea claramente definida.
Violación de SRP: Estructura "todopoderosa"
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 envío de email println!("Enviando reporte {} a {}", self.generate(), recipient); }}Este código viola SRP: la estructura Report es responsable de generar datos, guardarlos en un archivo y enviar emails. Cambiar la lógica de guardado o envío requeriría modificar el propio reporte.
Implementación correcta con 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 reporte {} a {}", report.generate(), recipient); }}Ahora cada estructura tiene una única responsabilidad: Report — generación, FileSaver — guardado, EmailSender — envío. Esto simplifica las pruebas y los cambios en el código.
2. Open/Closed Principle (OCP) — Principio de Abierto/Cerrado
OCP afirma: "Las clases deben estar abiertas para extensión, pero cerradas para modificación". En Rust, esto se logra mediante traits y genéricos.
Ejemplo: Sistema de procesamiento de pagos
trait PaymentProcessor { fn process(&self, amount: f64) -> String;}
struct CreditCardProcessor;
impl PaymentProcessor for CreditCardProcessor { fn process(&self, amount: f64) -> String { format!("Procesando {} mediante tarjeta de crédito", amount) }}
struct PayPalProcessor;
impl PaymentProcessor for PayPalProcessor { fn process(&self, amount: f64) -> String { format!("Procesando {} mediante PayPal", amount) }}
struct PaymentHandler<T: PaymentProcessor> { processor: T,}
impl<T: PaymentProcessor> PaymentHandler<T> { fn execute(&self, amount: f64) -> String { self.processor.process(amount) }}Añadir un nuevo método de pago (por ejemplo, CryptoProcessor) no requiere modificar el código existente — basta con implementar el trait PaymentProcessor.