Skip to content

Instantly share code, notes, and snippets.

@LaChivaloca69
Created October 10, 2025 00:22
Show Gist options
  • Select an option

  • Save LaChivaloca69/b8c6061ad50127933e6ecde4edc1d449 to your computer and use it in GitHub Desktop.

Select an option

Save LaChivaloca69/b8c6061ad50127933e6ecde4edc1d449 to your computer and use it in GitHub Desktop.

Alvarado Cardona Antonio

Tienda de Música Digital — Refactorización en Rust

Entregable: Código refactorizado en Rust + README explicativo.

README.md

Descripción del problema original

La aplicación original tenía clases concretas (Guitar, Piano, ...) y una clase principal MusicStore que usaba condicionales (strings con match / if) para decidir qué instrumento instanciar y reproducir. Cada vez que se agregaba un nuevo instrumento había que modificar MusicStore y otras partes del código.

Problemas detectados

  • Uso excesivo de condicionales para seleccionar tipos.
  • Alto acoplamiento entre MusicStore y las clases concretas.
  • Violación del principio Open/Closed: para añadir un tipo nuevo hay que cambiar código existente.
  • Falta de abstracción común para los instrumentos.
  • Dificultad para pruebas unitarias.

Objetivos aplicados

  • Eliminar condicionales fuera de la fábrica.
  • Introducir la abstracción Instrument como trait.
  • Implementar un Factory con registro (registro dinámico de constructores) para soportar Open/Closed.
  • MusicStore dependerá de una abstracción (InstrumentFactory) e inyectará la dependencia.
  • Facilitar pruebas mediante el uso de mocks/fakes implementando Instrument.

Patrones aplicados

  • Factory Method / Registry-based Factory: Se expone un trait InstrumentFactory y una implementación RegistryFactory que mantiene una tabla de constructores. Esto permite registrar nuevos instrumentos sin tocar MusicStore.

Principios SOLID aplicados

  • Single Responsibility: Cada struct/trait tiene una responsabilidad clara: Instrument (contrato), Guitar/Piano (implementación), RegistryFactory (creación/registro), MusicStore (uso de instrumentos).
  • Open/Closed: RegistryFactory permite añadir nuevas implementaciones registrando constructores, no cambiando MusicStore.
  • Liskov: Las implementaciones de Instrument respetan el contrato play().
  • Inversion of Dependencies: MusicStore depende de la abstracción InstrumentFactory en vez de clases concretas.
  • Dependency Inversion & Dependency Injection: MusicStore recibe el factory por constructor.

Cómo ejecutar

  1. Crear un nuevo proyecto con cargo new music_store.
  2. Reemplazar src/main.rs por el contenido provisto.
  3. cargo run para ver el ejemplo.
  4. cargo test para ejecutar pruebas.

Código Defectuoso - Sin patrones ni principios SOLID

Clases concretas sin abstracción común

struct Guitar;
impl Guitar {
    fn play(&self) {
        println!("Strumming the guitar 🎸");
    }
}

struct Piano;
impl Piano {
    fn play(&self) {
        println!("Playing the piano 🎹");
    }
}

struct Drums;
impl Drums {
    fn play(&self) {
        println!("Beating the drums 🥁");
    }
}

// Clase principal que decide qué instrumento crear
// Notar el uso de condicionales, violando Open/Closed
struct MusicStore;

impl MusicStore {
    fn play_instrument(&self, kind: &str) {
        if kind == "guitar" {
            let g = Guitar;
            g.play();
        } else if kind == "piano" {
            let p = Piano;
            p.play();
        } else if kind == "drums" {
            let d = Drums;
            d.play();
        } else {
            println!("Instrument not found ❌");
        }
    }
}

fn main() {
    let store = MusicStore;
    store.play_instrument("guitar");
    store.play_instrument("piano");
    store.play_instrument("drums");
    store.play_instrument("violin"); // no existe, rompe la extensibilidad
}

Problemas Identificados

Problema Descripción ❌ Uso excesivo de condicionales Cada nuevo instrumento requiere agregar otro else if. ❌ Alto acoplamiento MusicStore depende directamente de Guitar, Piano, Drums. ❌ Violación de Open/Closed No se pueden agregar nuevos instrumentos sin modificar el código existente. ❌ Falta de abstracción No existe un trait común (Instrument) que unifique las implementaciones. ❌ Dificultad para pruebas No es posible simular instrumentos o usar inyección de dependencias.

Código fuente (Rust)

A continuación se incluye una implementación completa en un solo archivo (src/main.rs) para facilitar la prueba.

Archivo: src/main.rs

use std::collections::HashMap;
use std::sync::{Arc, Mutex};

// -------------------------
// Trait: Instrument (abstracción)
// -------------------------
pub trait Instrument: Send + Sync {
    fn play(&self) -> String;
}

// -------------------------
// Implementaciones concretas
// -------------------------
pub struct Guitar {
    pub model: String,
}

impl Guitar {
    pub fn new(model: impl Into<String>) -> Self {
        Guitar { model: model.into() }
    }
}

impl Instrument for Guitar {
    fn play(&self) -> String {
        format!("Strumming the {} guitar 🎸", self.model)
    }
}

pub struct Piano {
    pub brand: String,
}

impl Piano {
    pub fn new(brand: impl Into<String>) -> Self {
        Piano { brand: brand.into() }
    }
}

impl Instrument for Piano {
    fn play(&self) -> String {
        format!("Playing a {} piano 🎹", self.brand)
    }
}

pub struct Drums {
    pub kit_name: String,
}

impl Drums {
    pub fn new(kit_name: impl Into<String>) -> Self {
        Drums { kit_name: kit_name.into() }
    }
}

impl Instrument for Drums {
    fn play(&self) -> String {
        format!("Beating the {} drums 🥁", self.kit_name)
    }
}

// -------------------------
// Factory abstraction
// -------------------------

// Un constructor devuelve un Box<dyn Instrument>
type Constructor = dyn Fn() -> Box<dyn Instrument> + Send + Sync;

pub trait InstrumentFactory: Send + Sync {
    fn create(&self, key: &str) -> Option<Box<dyn Instrument>>;
    fn register(&self, key: &str, constructor: Box<Constructor>);
}

// -------------------------
// RegistryFactory: permite registrar constructores dinámicamente
// cumple Open/Closed — para agregar instrumentos se registra uno nuevo
// -------------------------
pub struct RegistryFactory {
    registry: Mutex<HashMap<String, Box<Constructor>>>,
}

impl RegistryFactory {
    pub fn new() -> Self {
        RegistryFactory { registry: Mutex::new(HashMap::new()) }
    }
}

impl InstrumentFactory for RegistryFactory {
    fn create(&self, key: &str) -> Option<Box<dyn Instrument>> {
        let registry = self.registry.lock().unwrap();
        registry.get(key).map(|ctor| ctor())
    }

    fn register(&self, key: &str, constructor: Box<Constructor>) {
        let mut registry = self.registry.lock().unwrap();
        registry.insert(key.to_string(), constructor);
    }
}

// -------------------------
// MusicStore: consumidor de InstrumentFactory
// -------------------------
pub struct MusicStore {
    factory: Arc<dyn InstrumentFactory>,
}

impl MusicStore {
    pub fn new(factory: Arc<dyn InstrumentFactory>) -> Self {
        MusicStore { factory }
    }

    pub fn play_instrument(&self, kind: &str) -> Result<String, String> {
        match self.factory.create(kind) {
            Some(instr) => Ok(instr.play()),
            None => Err(format!("Instrument '{}' not found in factory", kind)),
        }
    }
}

// -------------------------
// Ejemplo de uso en main
// -------------------------
fn main() {
    let factory = Arc::new(RegistryFactory::new());

    // Registramos constructores para cada tipo de instrumento
    { // scope para ejemplo de closures
        let f = factory.clone();
        f.register("guitar", Box::new(|| Box::new(Guitar::new("Fender Stratocaster"))));
        f.register("piano", Box::new(|| Box::new(Piano::new("Yamaha"))));
        f.register("drums", Box::new(|| Box::new(Drums::new("Pearl Export"))));
    }

    let store = MusicStore::new(factory.clone());

    // Usos
    for kind in ["guitar", "piano", "drums", "violin"].iter() {
        match store.play_instrument(kind) {
            Ok(output) => println!("{} -> {}", kind, output),
            Err(err) => println!("{} -> ERROR: {}", kind, err),
        }
    }

    // Mostrar cómo un nuevo instrumento puede añadirse sin tocar MusicStore
    factory.register("ukulele", Box::new(|| Box::new(Guitar::new("Kala Ukelele"))));
    println!("ukulele -> {}", store.play_instrument("ukulele").unwrap());
}

// -------------------------
// Pruebas unitarias
// -------------------------
#[cfg(test)]
mod tests {
    use super::*;

    struct FakeInstrument;
    impl Instrument for FakeInstrument {
        fn play(&self) -> String { "fake play".to_string() }
    }

    #[test]
    fn test_music_store_uses_factory() {
        let factory = Arc::new(RegistryFactory::new());
        factory.register("fake", Box::new(|| Box::new(FakeInstrument)));

        let store = MusicStore::new(factory);
        let out = store.play_instrument("fake").unwrap();
        assert_eq!(out, "fake play");
    }

    #[test]
    fn test_missing_instrument() {
        let factory = Arc::new(RegistryFactory::new());
        let store = MusicStore::new(factory);
        let res = store.play_instrument("nope");
        assert!(res.is_err());
    }
}

Notas finales

  • La RegistryFactory centraliza la creación y mantiene un registro de constructores. Esto elimina condicionales en MusicStore y permite registrar nuevos instrumentos en tiempo de inicialización o por plugins.
  • Para proyectos más grandes se puede dividir en varios módulos (instruments::guitar, factory::registry, store::music_store) y agregar Cargo.toml con metadata.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment