Skip to content

Instantly share code, notes, and snippets.

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

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

Select an option

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

Alvarado Cardona Antonio 22210279

CellShop — Punto 1: Encapsulamiento de Mobile (Rust)

Descripción del problema original

En la versión defectuosa la clase Mobile exponía atributos públicos (pub) como brand, model, price, stock y banderas (has_extra_warranty, has_screen_protector). Esto permitía que cualquier parte del código leyera o modificara directamente el estado del objeto sin control ni validación, generando:

  • Violación del principio de encapsulamiento.
  • Riesgo de inconsistencias (por ejemplo, price negativo o stock negativo).
  • Dificultad para auditar cambios y añadir reglas de negocio (p. ej. validaciones).
  • Dificultad para migrar a una implementación que necesite invariantes más complejos.
// -------------------------
// CellShop - Código Defectuoso (ANTES)
// Lenguaje: Rust
// Propósito: ejemplificar 10 malas prácticas pedidas en la actividad
// -------------------------

// 1) Atributos públicos en Mobile (no encapsulado)
pub struct Mobile {
    pub brand: String,
    pub model: String,
    pub price: f64,
    pub stock: i32,
    // característica añadida directamente (no decorator)
    pub has_extra_warranty: bool,
    pub has_screen_protector: bool,
}

impl Mobile {
    // 5) Mobile se instancia directamente y sin fábrica
    pub fn new(brand: &str, model: &str, price: f64, stock: i32) -> Self {
        Mobile {
            brand: brand.to_string(),
            model: model.to_string(),
            price,
            stock,
            has_extra_warranty: false,
            has_screen_protector: false,
        }
    }

    // 2) Método largo y confuso: procesa venta, revisa promociones, factura, actualiza inventario
    pub fn process_sale(&mut self, qty: i32, customer_loyalty: bool, coupon: Option<&str>) -> String {
        // Validaciones básicas
        if qty <= 0 {
            return "Cantidad inválida".to_string();
        }
        if self.stock < qty {
            return "Stock insuficiente".to_string();
        }

        // 4 & 10) Lógica rígida de promociones (codificada por marca y encadenada aquí)
        // - Si la marca es \"BrandX\" aplica 10%
        // - Si hay loyalty aplica 5% adicional
        // - Si coupon == \"BLACKFRIDAY\" reemplaza por 25%
        let mut discount = 0.0;
        if self.brand == "BrandX" {
            discount += 0.10;
        }
        if customer_loyalty {
            discount += 0.05;
        }
        if let Some(code) = coupon {
            if code == "BLACKFRIDAY" {
                discount = 0.25; // sobreescribe otras reglas
            } else if code == "WELCOME10" {
                discount += 0.10;
            }
        }

        // 3 & 9) InventoryAndBilling todo junto (mezcla inventario y facturación)
        // 9) Facturación acoplada a implementación concreta (imprime directamente)
        let subtotal = self.price * (qty as f64);
        let total = subtotal * (1.0 - discount);

        // 7) Cambios de inventario no notifican a nadie (no observer)
        self.stock -= qty;

        // 6) StoreManager (puede haber múltiples instancias)
        // (ejemplo de uso más abajo)

        // 8) Añadir característica: modifico el móvil directamente (no decorator)
        if coupon == Some("ADD_WARRANTY") {
            self.has_extra_warranty = true; // cambia estado del objeto
        }

        // Imprimir factura (acoplamiento a salida)
        println!("--- FACTURA ---");
        println!("Producto: {} {} x{}", self.brand, self.model, qty);
        println!("Subtotal: {:.2}", subtotal);
        println!("Descuento: {:.2}%", discount * 100.0);
        println!("Total a pagar: {:.2}", total);
        println!("--------------");

        // 10) Lógica de promociones embebida y secuencial (difícil de componer)
        // Retorno simple
        format!("Venta procesada. Total: {:.2}", total)
    }
}

// 3) Clase mezclada InventoryAndBilling (SRP violado)
// maneja inventario y facturación a la vez.
pub struct InventoryAndBilling {
    pub items: Vec<Mobile>, // 1) campos públicos
}

impl InventoryAndBilling {
    pub fn new() -> Self {
        InventoryAndBilling { items: Vec::new() }
    }

    pub fn add_mobile(&mut self, mobile: Mobile) {
        self.items.push(mobile);
    }

    // Busca por marca y modelo - uso directo de strings (acoplamiento a implementación)
    pub fn find_mobile_mut(&mut self, brand: &str, model: &str) -> Option<&mut Mobile> {
        for m in &mut self.items {
            if m.brand == brand && m.model == model {
                return Some(m);
            }
        }
        None
    }

    // 2) Método que orquesta venta delegando a Mobile::process_sale (pero también factura)
    pub fn sell(&mut self, brand: &str, model: &str, qty: i32, loyalty: bool, coupon: Option<&str>) -> String {
        match self.find_mobile_mut(brand, model) {
            Some(mobile) => {
                // Actualiza inventario y genera \"factura\" imprimiendo
                let result = mobile.process_sale(qty, loyalty, coupon);
                // aquí podríamos además grabar en base de datos, enviar emails, etc.
                result
            }
            None => "Producto no encontrado".to_string(),
        }
    }
}

// 6) StoreManager con múltiples instancias posibles (no es Singleton)
pub struct StoreManager {
    pub name: String,
    // referencia directa a la implementación concreta InventoryAndBilling (acoplamiento)
    pub inventory_billing: InventoryAndBilling,
}

impl StoreManager {
    pub fn new(name: &str) -> Self {
        StoreManager {
            name: name.to_string(),
            inventory_billing: InventoryAndBilling::new(),
        }
    }

    pub fn report_stock(&self) {
        println!("Reporte de stock por el manager {}", self.name);
        for item in &self.inventory_billing.items {
            println!("{} {} - stock: {}", item.brand, item.model, item.stock);
        }
    }

    // Método que no notifica a otros módulos (no Observer)
    pub fn restock(&mut self, brand: &str, model: &str, qty: i32) {
        for m in &mut self.inventory_billing.items {
            if m.brand == brand && m.model == model {
                m.stock += qty;
            }
        }
    }
}

fn main() {
    // CREACIÓN DIRECTA DE OBJETOS (no factory) - Problema 5
    let mut samsung_a = Mobile::new("BrandX", "A100", 299.99, 10);
    let mut iphone = Mobile::new("BrandY", "IPhoneMini", 699.99, 5);

    // 3) InventoryAndBilling y StoreManager acoplados
    let mut inv_bill = InventoryAndBilling::new();
    inv_bill.add_mobile(samsung_a);
    inv_bill.add_mobile(iphone);

    // 6) Se crean múltiples managers (no singleton) — ejemplo de mala práctica
    let mut manager_juan = StoreManager::new("Juan");
    manager_juan.inventory_billing = inv_bill; // movimiento directo (propiedad pública)

    // Se crea otro manager sin compartir estado correctamente (posible inconsistencia)
    let mut manager_maria = StoreManager::new("Maria");
    // manager_maria tiene su propio InventoryAndBilling vacío -> inconsistencias reales

    // 2 & 10) Venta: todo embebido, promociones rígidas, facturación dentro del proceso
    let sale_result = manager_juan.inventory_billing.sell("BrandX", "A100", 2, true, Some("WELCOME10"));
    println!("Resultado venta: {}", sale_result);

    // 7) Cambio de inventario (restock) no notifica ni dispara eventos
    manager_juan.restock("BrandX", "A100", 5);
    manager_juan.report_stock();

    // 8) Añadir característica fija a Mobile (se usa campo booleano en la entidad)
    // en lugar de usar decorator o composición.
    if let Some(m) = manager_juan.inventory_billing.find_mobile_mut("BrandX", "A100") {
        m.has_screen_protector = true; // modificación directa
    }

    // 4) Promoción por marca rígida: difícil de cambiar sin tocar process_sale
    let sale_two = manager_juan.inventory_billing.sell("BrandY", "IPhoneMini", 1, false, Some("BLACKFRIDAY"));
    println!("Resultado venta 2: {}", sale_two);

    // 9) Código altamente acoplado y con lógica dispersa:
    // - muchas responsabilidades en InventoryAndBilling
    // - StoreManager manipula estructura interna directamente
    // - no existe una fachada simple para operaciones comunes
}

Corrida de codigo malo

image

Tabla resumen de los 10 problemas (para tu README)

Problema detectado Dónde se ve en el código Recomendación / Patrón GoF sugerido
1 Atributos públicos en Mobile Mobile con campos pub Encapsular con getters/setters o private + métodos; buenas prácticas OOP
2 process_sale largo y confuso Mobile::process_sale Aplicar SRP y extraer responsabilidades a métodos/objetos (Billing, Promotion)
3 InventoryAndBilling mezcla inventario y facturación InventoryAndBilling Separar en Inventory y Billing (SRP)
4 Promotion rígida por marca Lógica de descuentos en process_sale Strategy Pattern para promociones configurables
5 Mobile se instancia directamente Mobile::new(...) en main Usar Factory Method o una Factory para creación de móviles
6 Múltiples instancias de StoreManager manager_juan, manager_maria Singleton (o inyección de dependencia/shared state) si se necesita instancia única
7 Cambios de inventario no notifican a otros módulos restock y process_sale Observer Pattern para notificar inventario, UI, logs, etc.
8 Añadir características fijas al Mobile campos has_extra_warranty, has_screen_protector Decorator Pattern para añadir funcionalidades sin modificar la clase
9 Clases fuertemente acopladas a implementaciones concretas StoreManager.inventory_billing manipulado directamente Facade Pattern para simplificar API y desacoplar consumidores
10 Lógica de promociones embebida en la venta Promociones y chaining en process_sale Chain of Responsibility para componer promociones y reglas secuenciales
// src/main.rs
// CellShop - Refactor Punto 1: Encapsular atributos en Mobile
// Autor: Alvarado Cardona Antonio 22210279
// Comentarios en español explicando la decisión y el propósito.

use std::fmt;

// La estructura Mobile ahora tiene campos privados (por defecto en Rust).
// Acceso y mutación controlados por métodos públicos -> encapsulamiento.
pub struct Mobile {
    brand: String,
    model: String,
    price: f64,
    stock: i32,
    // características que antes eran públicas; ahora privadas gestionadas por métodos
    extra_warranty: bool,
    screen_protector: bool,
}

impl Mobile {
    /// Constructor público que valida valores básicos.
    pub fn new(brand: impl Into<String>, model: impl Into<String>, price: f64, stock: i32) -> Self {
        let price = if price < 0.0 { 0.0 } else { price };
        let stock = if stock < 0 { 0 } else { stock };

        Mobile {
            brand: brand.into(),
            model: model.into(),
            price,
            stock,
            extra_warranty: false,
            screen_protector: false,
        }
    }

    // ----------------------------
    // Getters (solo lectura)
    // ----------------------------
    pub fn brand(&self) -> &str {
        &self.brand
    }

    pub fn model(&self) -> &str {
        &self.model
    }

    pub fn price(&self) -> f64 {
        self.price
    }

    pub fn stock(&self) -> i32 {
        self.stock
    }

    pub fn has_extra_warranty(&self) -> bool {
        self.extra_warranty
    }

    pub fn has_screen_protector(&self) -> bool {
        self.screen_protector
    }

    // ----------------------------
    // Setters / mutators controlados
    // ----------------------------
    /// Actualiza el precio si es válido (no negativo).
    pub fn set_price(&mut self, new_price: f64) -> Result<(), String> {
        if new_price < 0.0 {
            Err("El precio no puede ser negativo".into())
        } else {
            self.price = new_price;
            Ok(())
        }
    }

    /// Ajusta el stock (puede sumar o restar). No permite stock negativo.
    pub fn adjust_stock(&mut self, delta: i32) -> Result<(), String> {
        let new_stock = self.stock + delta;
        if new_stock < 0 {
            Err("No se puede reducir el stock por debajo de 0".into())
        } else {
            self.stock = new_stock;
            Ok(())
        }
    }

    /// Marca que el móvil tiene garantía extra (ej.: al vender un add-on).
    pub fn enable_extra_warranty(&mut self) {
        self.extra_warranty = true;
    }

    /// Remueve la garantía extra.
    pub fn disable_extra_warranty(&mut self) {
        self.extra_warranty = false;
    }

    /// Marca que el móvil tiene protector de pantalla.
    pub fn set_screen_protector(&mut self, enabled: bool) {
        self.screen_protector = enabled;
    }

    // ----------------------------
    // Operaciones relacionadas (ejemplos)
    // ----------------------------
    /// Intenta vender una cantidad; devuelve el total calculado o error.
    /// Nota: aquí solo se gestiona stock/total; la facturación real debería estar en otra clase (SRP).
    pub fn sell(&mut self, qty: i32) -> Result<f64, String> {
        if qty <= 0 {
            return Err("Cantidad a vender debe ser mayor que 0".into());
        }
        if self.stock < qty {
            return Err("Stock insuficiente".into());
        }

        let total = (self.price) * (qty as f64);
        // disminuir stock de forma controlada
        self.adjust_stock(-qty)?;
        Ok(total)
    }
}

// Implementamos Display para facilitar logging/printings controlados
impl fmt::Display for Mobile {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} {} - Precio: {:.2} - Stock: {}{}{}",
            self.brand,
            self.model,
            self.price,
            self.stock,
            if self.extra_warranty { " - Garantía Extra" } else { "" },
            if self.screen_protector { " - Protector" } else { "" }
        )
    }
}

fn main() {
    // Ejemplo de uso del Mobile refactorizado
    let mut m = Mobile::new("BrandX", "A100", 299.99, 10);
    println!("Móvil creado: {}", m);

    // Lectura vía getters
    println!("Brand: {}, Model: {}", m.brand(), m.model());
    println!("Precio actual: {:.2}", m.price());
    println!("Stock actual: {}", m.stock());

    // Intento de cambiar precio válido
    match m.set_price(279.99) {
        Ok(_) => println!("Precio actualizado a {:.2}", m.price()),
        Err(e) => println!("Error al actualizar precio: {}", e),
    }

    // Venta de 2 unidades
    match m.sell(2) {
        Ok(total) => println!("Venta procesada. Total: {:.2}", total),
        Err(e) => println!("Error en venta: {}", e),
    }

    println!("Estado final: {}", m);
}

// ----------------------------
// Tests unitarios para verificar el encapsulamiento/validaciones
// ----------------------------
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn create_mobile_and_getters() {
        let m = Mobile::new("BrandY", "ModelZ", 100.0, 5);
        assert_eq!(m.brand(), "BrandY");
        assert_eq!(m.model(), "ModelZ");
        assert_eq!(m.price(), 100.0);
        assert_eq!(m.stock(), 5);
        assert!(!m.has_extra_warranty());
        assert!(!m.has_screen_protector());
    }

    #[test]
    fn set_price_negative_fails() {
        let mut m = Mobile::new("B", "M", 50.0, 1);
        assert!(m.set_price(-10.0).is_err());
        assert_eq!(m.price(), 50.0); // no cambió
    }

    #[test]
    fn adjust_stock_and_sell() {
        let mut m = Mobile::new("B", "M", 20.0, 3);
        assert!(m.adjust_stock(2).is_ok());
        assert_eq!(m.stock(), 5);

        // vender 4 -> OK
        let total = m.sell(4).unwrap();
        assert_eq!(total, 80.0);
        assert_eq!(m.stock(), 1);

        // vender más que stock -> error
        assert!(m.sell(2).is_err());
    }

    #[test]
    fn warranty_and_protector_flags() {
        let mut m = Mobile::new("B", "M", 10.0, 1);
        assert!(!m.has_extra_warranty());
        m.enable_extra_warranty();
        assert!(m.has_extra_warranty());

        assert!(!m.has_screen_protector());
        m.set_screen_protector(true);
        assert!(m.has_screen_protector());
    }
}

Corrida del codigo corregido

image

Cambios realizados

  • Los campos de Mobile son privados (visibilidad por defecto en Rust).
  • Se han añadido getters para exponer información de solo lectura:
    • brand(), model(), price(), stock(), has_extra_warranty(), has_screen_protector().
  • Se han añadido mutadores controlados (setters/operaciones) con validaciones:
    • set_price(new_price) — rechaza precios negativos.
    • adjust_stock(delta) — evita stock negativo.
    • enable_extra_warranty() / disable_extra_warranty()
    • set_screen_protector(enabled)
  • Se añadió un método sell(qty) que gestiona la lógica mínima de venta (validaciones de cantidad y stock) y actualiza el stock a través de adjust_stock.
  • Se implementó Display para obtener una representación segura/controlada del objeto.
  • Se añadieron tests unitarios que validan las propiedades y las restricciones.

Nota: el método sell es una implementación mínima para el ejemplo. En la refactorización completa deberías separar facturación y ventas en clases distintas para respetar SRP.

¿Por qué esto mejora el diseño?

  • Encapsulamiento: el estado interno ya no puede ser alterado arbitrariamente; todos los cambios pasan por métodos que pueden validar e imponer invariantes.
  • Seguridad: se evita que price o stock adopten valores inválidos.
  • Facilita pruebas: los tests pueden verificar comportamiento observado a través de la API pública en lugar de tocar campos internos.
  • Facilita futuras refactorizaciones: si luego se necesita persistir cambios, emitir eventos o añadir reglas, basta con ampliar los métodos en vez de buscar todos los accesos directos a campos públicos.
  • Prepara terreno para patrones posteriores: con Mobile encapsulado es más fácil introducir Decorator, Factory o Strategy porque la API pública es estable y controlada.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment