Composizione
Fa parte della sezione Programmazione orientata agli oggetti del percorso Rust di Coddy. Lezione 55 di 61.
Molti linguaggi orientati agli oggetti si basano molto sull'ereditarietà per condividere funzionalità tra i tipi. Rust adotta un approccio diverso: la composizione. Invece di ereditare il comportamento da un genitore, costruisci tipi complessi includendo altri tipi come campi.
Considera di modellare un'auto. Invece di creare una classe Vehicle di base ed ereditarne, componi una Car a partire da componenti più piccoli e specializzati:
struct Engine {
horsepower: u32,
}
struct Wheels {
count: u8,
diameter: f32,
}
struct Car {
engine: Engine,
wheels: Wheels,
brand: String,
}
Car non eredita da Engine o Wheels. Li ha. Questa relazione di tipo «ha un» è la composizione. Puoi accedere ai componenti interni tramite i loro campi e chiamare direttamente i loro metodi:
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // Delega al componente interno
}
}
Questo schema mantiene ogni struct concentrata su una singola responsabilità. Devi cambiare il funzionamento dei motori? Modifica solo la struct Engine. Vuoi riutilizzare Wheels per una motocicletta? Ti basta includerla in una nuova struct. La composizione ti offre flessibilità senza l’accoppiamento stretto che spesso crea l’ereditarietà.
Sfida
FacileCostruiamo un sistema informatico usando la composizione! Invece di creare un'unica struct enorme, comporrai una Computer a partire da componenti più piccoli e specifici: una CPU e una Memory. Ogni componente saprà descriversi e Computer delegherà il compito alle sue parti.
Organizzerai il codice in tre file:
cpu.rs: Definisci una struct pubblicaCPUcon due campi pubblici:cores(u32) per il numero di core espeed_ghz(f32) per la velocità di clock. Aggiungi un metodospecsche restituisce una String che descrive la CPU nel formato{cores}-core @ {speed_ghz}GHz.memory.rs: Definisci una struct pubblicaMemorycon un campo pubblicosize_gb(u32) per la dimensione della memoria. Aggiungi un metodospecsche restituisce una String nel formato{size_gb}GB RAM.main.rs: Includi entrambi i moduli e crea una struct pubblicaComputerche ha un campocpudi tipoCPUe un campomemorydi tipoMemory. Implementa un metodosystem_infoper Computer che delega ai suoi componenti e stampa le specifiche complete del sistema. Usa gli input forniti per creare un Computer e visualizzarne le informazioni.
Il metodo system_info dovrebbe stampare:
System: {cpu_specs} | {memory_specs}Per esempio, con gli input 8, 3.5 e 16:
System: 8-core @ 3.5GHz | 16GB RAME con gli input 4, 2.8 e 32:
System: 4-core @ 2.8GHz | 32GB RAMRiceverai tre input: i core della CPU (da analizzare come u32), la velocità della CPU (da analizzare come f32) e la dimensione della memoria (da analizzare come u32).
Provalo tu
mod cpu;
mod memory;
use cpu::CPU;
use memory::Memory;
// TODO: Definisci una struct pubblica Computer con due campi pubblici:
// - cpu: CPU
// - memory: Memory
// TODO: Implementa un metodo system_info su Computer che:
// - Deleghi a cpu.specs() e memory.specs()
// - Stampi: 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 istanze di CPU e Memory usando gli input analizzati
// TODO: Crea un'istanza di Computer con CPU e Memory
// TODO: Chiama system_info() sul computer per stampare il risultato
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Metodi e comportamento
Introduzione ai blocchi implIl parametro selfMetodi mutabiliFunzioni associateBlocchi impl multipliConcatenamento di metodiRiepilogo - Azioni sul rettangolo4Progetto: animale domestico virtuale
Definire l'animaleNutrire l'animale7Trait standard
Il trait DebugIl trait DisplayClone e CopyTrait di uguaglianzaRiepilogo - Punto stampabile10Progetto: sistema di documenti
Il trait DrawComponente di testo2Incapsulamento e moduli
Nozioni di base sui moduliLa parola chiave publicCampi privatiGetterSetterRiepilogo - Armadietto sicuro5Generics
Struct genericheMetodi genericiPiù tipi genericiFunzioni genericheRipasso - Punto di coordinate8I Trait come vincoli
Sintassi dei vincoli sui traitVincoli multipliLa clausola whereRestituire tipi con i traitRiepilogo - Stampante generica11Modelli di progettazione in Rust
Pattern NewtypeComposizioneIl trait DropFrom e IntoRipasso - Mock con smart pointer3Enumerazioni avanzate
Enumerazioni con datiMetodi sulle enumerazioniCorrispondenza delle varianti con datiL'enumerazione Option ripresaRiepilogo - enumerazione Shape6Definizione dei trait
Che cos’è un trait?Implementare i traitImplementazioni predefiniteSovrascrivere le implementazioni predefiniteTrait con parametriRiepilogo - Lettore multimedialeEsercitati da solo: Compilatore Rust online