Patrones de diseño en Rust con ejemplos
Los patrones de diseño son soluciones probadas para problemas típicos en el desarrollo de software. En Rust, debido a su sistema único de propiedad, préstamo y tiempos de vida, la implementación de los patrones clásicos de GoF a menudo requiere un enfoque especial. En este artículo, analizaremos varios patrones populares con ejemplos de código real en Rust.
¿Por qué Rust cambia el enfoque hacia los patrones?
A diferencia de los lenguajes con recolección de basura (Java, C#), Rust impone reglas estrictas sobre la propiedad de la memoria. Esto hace que algunas implementaciones clásicas (por ejemplo, Singleton a través de estado mutable global) sean inseguras o ineficientes. Sin embargo, Rust ofrece sus propias alternativas elegantes, basadas en tipos y traits.
1. Builder (Constructor) — creación paso a paso de objetos
Builder es uno de los patrones más utilizados en Rust. Es ideal para la configuración de estructuras complejas, especialmente cuando hay muchos campos opcionales.
Ejemplo: creación de una solicitud HTTP
struct Request { method: String, url: String, headers: Vec<(String, String)>, body: Option<String>,}
struct RequestBuilder { method: String, url: String, headers: Vec<(String, String)>, body: Option<String>,}
impl RequestBuilder { fn new(method: &str, url: &str) -> Self { Self { method: method.to_string(), url: url.to_string(), headers: Vec::new(), body: None, } }
fn header(mut self, key: &str, value: &str) -> Self { self.headers.push((key.to_string(), value.to_string())); self }
fn body(mut self, body: &str) -> Self { self.body = Some(body.to_string()); self }
fn build(self) -> Request { Request { method: self.method, url: self.url, headers: self.headers, body: self.body, } }}
fn main() { let req = RequestBuilder::new("POST", "https://api.example.com/data") .header("Content-Type", "application/json") .header("Authorization", "Bearer token123") .body(r#"{"key": "value"}"#) .build();
println!("URL: {}", req.url); println!("Headers count: {}", req.headers.len());}Ventajas del enfoque en Rust
- Seguridad: el objeto se crea solo después de llamar a
build()— una configuración incompleta es imposible. - Legibilidad: la cadena de métodos es intuitivamente comprensible.
- Seguridad de tipos: el compilador verifica los tipos en cada paso.
2. Strategy (Estrategia) — selección de algoritmo sobre la marcha
Strategy permite cambiar el comportamiento de un objeto durante la ejecución. En Rust, esto se implementa a través de traits y despacho dinámico (Box con traits) o estático (genéricos).
Ejemplo: ordenación de datos
trait SortStrategy { fn sort(&self, data: &mut [i32]);}
struct BubbleSort;struct QuickSort;
impl SortStrategy for BubbleSort { fn sort(&self, data: &mut [i32]) { // Implementación simplificada de ordenación de burbuja for i in 0..data.len() { for j in 0..data.len() - 1 - i { if data[j] > data[j + 1] { data.swap(j, j + 1); } } } }}
impl SortStrategy for QuickSort { fn sort(&self, data: &mut [i32]) { // Ordenación rápida (simplificada) if data.len() > 1 { let pivot = data.len() / 2; data.sort(); // Usamos la incorporada para el ejemplo } }}
struct Sorter { strategy: Box<dyn SortStrategy>,}
impl Sorter { fn new(strategy: Box<dyn SortStrategy>) ->