Composición
Parte de la sección Programación Orientada a Objetos del Journey de Rust de Coddy. Lección 55 de 61.
Muchos lenguajes orientados a objetos dependen en gran medida de la herencia para compartir funcionalidades entre tipos. Rust adopta un enfoque diferente: la composición. En lugar de heredar el comportamiento de un elemento padre, construyes tipos complejos incluyendo otros tipos como campos.
Considera modelar un automóvil. En lugar de crear una clase base Vehicle y heredar de ella, compones un Car a partir de componentes más pequeños y enfocados:
struct Engine {
horsepower: u32,
}
struct Wheels {
count: u8,
diameter: f32,
}
struct Car {
engine: Engine,
wheels: Wheels,
brand: String,
}
Car no hereda de Engine ni de Wheels. Los tiene. Esta relación de «tiene-un» es la composición. Accedes a los componentes internos a través de sus campos y puedes llamar directamente a sus métodos:
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // Delega al componente interno
}
}
Este patrón mantiene cada estructura centrada en una única responsabilidad. ¿Necesitas cambiar cómo funcionan los motores? Modifica únicamente la estructura Engine. ¿Quieres reutilizar Wheels para una motocicleta? Simplemente inclúyela en una estructura nueva. La composición te proporciona flexibilidad sin el acoplamiento estrecho que la herencia suele crear.
Desafío
Fácil¡Construyamos un sistema informático usando composición! En lugar de crear una estructura enorme, compondrás un Computer a partir de componentes más pequeños y especializados: un CPU y Memory. Cada componente sabrá cómo describirse a sí mismo, y Computer delegará en sus partes.
Organizarás tu código en tres archivos:
cpu.rs: Define una estructura públicaCPUcon dos campos públicos:cores(u32) para el número de núcleos yspeed_ghz(f32) para la velocidad de reloj. Añade un métodospecsque devuelva un String describiendo el CPU en el formato{cores}-core @ {speed_ghz}GHz.memory.rs: Define una estructura públicaMemorycon un campo públicosize_gb(u32) para el tamaño de la memoria. Añade un métodospecsque devuelva un String en el formato{size_gb}GB RAM.main.rs: Incluye ambos módulos y crea una estructura públicaComputerque tenga un campocpude tipoCPUy un campomemoryde tipoMemory. Implementa un métodosystem_infoen Computer que delegue en sus componentes e imprima la especificación completa del sistema. Usa las entradas proporcionadas para construir un Computer y mostrar su información.
El método system_info debe imprimir:
System: {cpu_specs} | {memory_specs}Por ejemplo, con las entradas 8, 3.5 y 16:
System: 8-core @ 3.5GHz | 16GB RAMY con las entradas 4, 2.8 y 32:
System: 4-core @ 2.8GHz | 32GB RAMRecibirás tres entradas: núcleos del CPU (analízalos como u32), velocidad del CPU (analízala como f32) y tamaño de la memoria (analízalo como u32).
Pruébalo tú mismo
mod cpu;
mod memory;
use cpu::CPU;
use memory::Memory;
// TODO: Define una struct pública Computer con dos campos públicos:
// - cpu: CPU
// - memory: Memory
// TODO: Implementa un método system_info en Computer que:
// - Delega a cpu.specs() y memory.specs()
// - Imprime: System: {cpu_specs} | {memory_specs}
fn main() {
let mut input = String::new();
std::io::stdin().read_line(&mut input).expect("Failed to read line");
let cores: u32 = input.trim().parse().expect("Invalid number");
input.clear();
std::io::stdin().read_line(&mut input).expect("Failed to read line");
let speed_ghz: f32 = input.trim().parse().expect("Invalid number");
input.clear();
std::io::stdin().read_line(&mut input).expect("Failed to read line");
let size_gb: u32 = input.trim().parse().expect("Invalid number");
// TODO: Crea instancias de CPU y Memory usando las entradas analizadas
// TODO: Crea una instancia de Computer con el CPU y la Memory
// TODO: Llama a system_info() en el computer para imprimir el resultado
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Métodos y Comportamiento
Intro a Bloques de ImplementaciónEl Parámetro SelfMétodos MutablesFunciones AsociadasMúltiples Bloques de ImplementaciónEncadenamiento de MétodosResumen - Acciones de Rectángulo4Proyecto: Mascota virtual
Definir la mascotaAlimentar a la mascota7Traits estándar
El trait DebugEl trait DisplayClone y CopyTraits de igualdadResumen - Punto imprimible10Proyecto: Sistema de documentos
El Trait DrawComponente de texto2Encapsulamiento y módulos
Conceptos básicos de módulosLa palabra clave PublicCampos privadosGettersSettersRepaso - Casillero seguro5Genéricos
Estructuras genéricasMétodos genéricosMúltiples tipos genéricosFunciones genéricasResumen - Punto de coordenadas8Traits como límites
Sintaxis de límites de traitsLímites múltiplesLa cláusula whereRetornar tipos con traitsResumen: Impresora genérica11Patrones de diseño en Rust
Patrón NewtypeComposiciónEl Trait DropFrom e IntoResumen - Mock de Smart Pointer3Enums avanzados
Enums con datosMétodos en EnumsCoincidencia de variantes de datosEl Enum Option revisitadoResumen - Enum Shape6Definición de Traits
¿Qué es un Trait?Implementación de TraitsImplementaciones por defectoSobrescribir valores por defectoTraits con parámetrosResumen - Reproductor multimediaPractica por tu cuenta: Compilador de Rust online