Composición
Parte de la sección Object Oriented Programming 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 funcionalidad entre tipos. Rust adopta un enfoque diferente: composición. En lugar de heredar el comportamiento de un padre, construyes tipos complejos incluyendo otros tipos como campos.
Considera modelar un coche. 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,
}
El Car no hereda de Engine o Wheels; los tiene. Esta relación "tiene-un" es composición. Accedes a los componentes internos a través de sus campos y puedes llamar a sus métodos directamente:
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // Delegar al componente interno
}
}
Este patrón mantiene cada struct enfocado en una sola responsabilidad. ¿Necesitas cambiar cómo funcionan los motores? Modifica solo el struct Engine. ¿Quieres reutilizar Wheels para una motocicleta? Simplemente inclúyelo en un nuevo struct. La composición te brinda flexibilidad sin el acoplamiento fuerte que la herencia suele crear.
Desafío
Fácil¡Vamos a construir un sistema informático utilizando composición! En lugar de crear una única estructura masiva, compondrás un Computer a partir de componentes más pequeños y enfocados: un CPU y Memory. Cada componente sabrá cómo describirse a sí mismo, y el Computer delegará en sus partes.
Organizarás tu código en tres archivos:
cpu.rs: Define una estructuraCPUpública con 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 la CPU en el formato"{cores}-core @ {speed_ghz}GHz".memory.rs: Define una estructuraMemorypública con 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: Importa ambos módulos y crea una estructuraComputerpública que 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. Utiliza 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 de CPU (analizar como u32), velocidad de CPU (analizar como f32) y tamaño de memoria (analizar como u32).
Hoja de referencia
Rust utiliza la composición en lugar de la herencia para compartir funcionalidad entre tipos. Se construyen tipos complejos incluyendo otros tipos como campos, creando una relación de tipo "tiene un" (has-a).
Composición Básica
Define structs más pequeños y enfocados, y composiciónalos en otros más grandes:
struct Engine {
horsepower: u32,
}
struct Wheels {
count: u8,
diameter: f32,
}
struct Car {
engine: Engine,
wheels: Wheels,
brand: String,
}
Delegación a Componentes
Accede a los componentes internos a través de sus campos y llama a sus métodos:
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // Delegate to the inner component
}
}
La composición mantiene cada struct enfocado en una sola responsabilidad, proporcionando flexibilidad sin un acoplamiento fuerte.
Pruébalo tú mismo
mod cpu;
mod memory;
use cpu::CPU;
use memory::Memory;
// TODO: Define una struct Computer pública con dos campos públicos:
// - cpu: CPU
// - memory: Memory
// TODO: Implementa un método system_info en Computer que:
// - Delegue a cpu.specs() y memory.specs()
// - Imprima: 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 procesadas
// TODO: Crea una instancia de Computer con la 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 Object Oriented Programming
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 multimedia