Principios SOLID en Rust: teoría y práctica de la A a la Z

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: ¿Por qué SOLID es importante en Rust?

SOLID son cinco principios fundamentales del 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 (no tiene herencia de clases en el sentido tradicional), estos principios son aplicables aquí gracias a los traits, structs, genéricos y la propiedad de memoria. En este artículo, analizaremos cada principio SOLID con ejemplos de código en Rust, mostraremos cómo implementarlos y explicaremos por qué mejoran su código.



1. Single Responsibility Principle (SRP): Principio de responsabilidad única

Esencia: Cada módulo, estructura o función debe tener una sola razón para cambiar.

En Rust, esto a menudo se viola cuando una sola estructura se encarga del almacenamiento de datos, su serialización y la lógica de negocio. El enfoque correcto es dividir las responsabilidades.



Ejemplo incorrecto (violación de SRP)

struct User {    name: String,    email: String,}

impl User { fn save_to_db(&self) { // lógica de guardado en base de datos }

fn send_email(&self, message: &str) { // lógica de envío de email }}

Aquí User se encarga tanto del almacenamiento como del envío: dos razones para cambiar.



Ejemplo correcto (cumplimiento de SRP)

struct User {    name: String,    email: String,}

struct UserRepository;impl UserRepository { fn save(&self, user: &User) { /* ... */ }}

struct EmailService;impl EmailService { fn send(&self, user: &User, message: &str) { /* ... */ }}

Ahora cada estructura tiene una única responsabilidad: User — datos, UserRepository — almacenamiento, EmailService — envío.



2. Open-Closed Principle (OCP): Principio de abierto/cerrado

Esencia: Las entidades de software deben estar abiertas para la extensión, pero cerradas para la modificación.

En Rust, esto se logra mediante traits y genéricos. Puede agregar nuevo comportamiento sin cambiar el código existente.



Ejemplo: Sistema de descuentos

Supongamos que tenemos una función para calcular el precio con descuento. En lugar de agregar nuevos tipos de descuento mediante if-else, usamos un trait:



trait Discount {    fn apply(&self, price: f64) -> f64;}

struct NoDiscount;impl Discount for NoDiscount { fn apply(&self, price: f64) -> f64 { price }}

struct SeasonalDiscount(f64);impl Discount for SeasonalDiscount { fn apply(&self, price: f64) -> f64 { price * (1.0 - self.0) }}

fn calculate_price(price: f64, discount: &dyn Discount) -> f64 { discount.apply(price)}

Ahora, para agregar un nuevo descuento (por ejemplo, BlackFridayDiscount), no necesitamos modificar calculate_price — simplemente implementamos el trait Discount.



3. Liskov Substitution Principle (LSP): Principio de sustitución de Barbara Liskov

Esencia: Los objetos de subtipos (en Rust, tipos que implementan un trait) deben ser intercambiables con objetos del tipo base sin romper la corrección del programa.

En Rust, esto significa que si tiene un trait y varias implementaciones, el código que trabaja con dyn Trait o genéricos no debe fallar al sustituir cualquiera de ellas.



Ejemplo de violación de LSP

trait Bird {    fn fly(&self) -> String;}

struct Sparrow;impl Bird for Sparrow { fn fly(&self) -> String { "Sparrow flies".to_string() }}

struct Penguin;impl Bird for Penguin { fn fly(&self) -> String { panic!("Penguins can't fly!") }}

Si reemplazamos Sparrow por Penguin, el código se rompe. La solución es no incluir métodos que no puedan ser implementados por todos los subtipos. Es mejor dividir los traits:



trait CanFly {    fn fly(&self) -> Stri                

Blogs

Book Recommendations