Composition
Fait partie de la section Object Oriented Programming du Journey Rust de Coddy — leçon 55 sur 61.
De nombreux langages orientés objet s'appuient fortement sur l'héritage pour partager des fonctionnalités entre les types. Rust adopte une approche différente : la composition. Au lieu d'hériter du comportement d'un parent, vous construisez des types complexes en incluant d'autres types en tant que champs.
Considérez la modélisation d'une voiture. Plutôt que de créer une classe de base Vehicle et d'en hériter, vous composez une Car à partir de composants plus petits et ciblés :
struct Engine {
horsepower: u32,
}
struct Wheels {
count: u8,
diameter: f32,
}
struct Car {
engine: Engine,
wheels: Wheels,
brand: String,
}
La classe Car n'hérite pas de Engine ou de Wheels — elle les a. Cette relation « a un » (has-a) est la composition. Vous accédez aux composants internes via leurs champs et pouvez appeler leurs méthodes directement :
impl Engine {
fn start(&self) {
println!("Engine with {} HP started!", self.horsepower);
}
}
impl Car {
fn start(&self) {
self.engine.start(); // Déléguer au composant interne
}
}
Ce modèle permet à chaque struct de rester concentrée sur une seule responsabilité. Besoin de changer le fonctionnement des moteurs ? Modifiez uniquement la struct Engine. Vous voulez réutiliser Wheels pour une moto ? Il suffit de l'inclure dans une nouvelle struct. La composition vous offre de la flexibilité sans le couplage étroit que l'héritage crée souvent.
Défi
FacileConstruisons un système informatique en utilisant la composition ! Au lieu de créer une seule structure massive, vous allez composer un Computer à partir de composants plus petits et ciblés : un CPU et une Memory. Chaque composant saura comment se décrire lui-même, et le Computer déléguera la tâche à ses parties.
Vous organiserez votre code sur trois fichiers :
cpu.rs: Définissez une structure publiqueCPUavec deux champs publics :cores(u32) pour le nombre de cœurs, etspeed_ghz(f32) pour la vitesse d'horloge. Ajoutez une méthodespecsqui retourne une String décrivant le CPU au format"{cores}-core @ {speed_ghz}GHz".memory.rs: Définissez une structure publiqueMemoryavec un champ publicsize_gb(u32) pour la taille de la mémoire. Ajoutez une méthodespecsqui retourne une String au format"{size_gb}GB RAM".main.rs: Importez les deux modules et créez une structure publiqueComputerqui possède un champcpude typeCPUet un champmemoryde typeMemory. Implémentez une méthodesystem_infosur Computer qui délègue à ses composants et affiche la spécification complète du système. Utilisez les entrées fournies pour construire un Computer et afficher ses informations.
La méthode system_info doit afficher :
System: {cpu_specs} | {memory_specs}Par exemple, avec les entrées 8, 3.5, et 16 :
System: 8-core @ 3.5GHz | 16GB RAMEt avec les entrées 4, 2.8, et 32 :
System: 4-core @ 2.8GHz | 32GB RAMVous recevrez trois entrées : les cœurs du CPU (analyser comme u32), la vitesse du CPU (analyser comme f32), et la taille de la mémoire (analyser comme u32).
Aide-mémoire
Rust utilise la composition au lieu de l'héritage pour partager des fonctionnalités entre les types. Vous construisez des types complexes en incluant d'autres types comme champs, créant ainsi une relation de type « a un » (has-a).
Composition de base
Définissez des structures plus petites et ciblées, puis composez-les pour en former de plus grandes :
struct Engine {
horsepower: u32,
}
struct Wheels {
count: u8,
diameter: f32,
}
struct Car {
engine: Engine,
wheels: Wheels,
brand: String,
}
Délégation aux composants
Accédez aux composants internes via leurs champs et appelez leurs méthodes :
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 composition permet à chaque structure de rester concentrée sur une seule responsabilité, offrant ainsi de la flexibilité sans couplage étroit.
Essayez vous-même
mod cpu;
mod memory;
use cpu::CPU;
use memory::Memory;
// TODO: Définir une struct Computer publique avec deux champs publics :
// - cpu: CPU
// - memory: Memory
// TODO: Implémenter une méthode system_info sur Computer qui :
// - Délègue à cpu.specs() et memory.specs()
// - Affiche : 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: Créer des instances de CPU et Memory en utilisant les entrées analysées
// TODO: Créer une instance de Computer avec le CPU et la Memory
// TODO: Appeler system_info() sur l'ordinateur pour afficher le résultat
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Object Oriented Programming
1Méthodes et comportement
Introduction aux blocs d'implémentationLe paramètre SelfMéthodes mutablesFonctions associéesBlocs d'implémentation multiplesChaînage de méthodesRécapitulatif - Actions sur le rectangle4Projet : Animal virtuel
Définir l'animalNourrir l'animal7Traits standards
Le trait DebugLe trait DisplayClone et CopyLes traits d'égalitéRécapitulatif - Point affichable10Projet : Système de documents
Le trait DrawComposant texte2Encapsulation et modules
Bases des modulesLe mot-clé publicChamps privésGettersSettersRécapitulatif - Casier sécurisé5Généricité
Structures génériquesMéthodes génériquesPlusieurs types génériquesFonctions génériquesRécapitulatif - Point de coordonnées8Les Traits comme contraintes
Syntaxe des contraintes de traitContraintes multiplesLa clause whereRetourner des types avec des traitsRécapitulatif - Afficheur générique11Design Patterns en Rust
Le pattern NewtypeCompositionLe trait DropFrom et IntoRécapitulatif - Mock de Smart Pointer3Enums avancées
Enums avec donnéesMéthodes sur les EnumsMatching des variantes de donnéesL'Enum Option revisitéeRécapitulatif - Enum Shape6Définition des Traits
Qu'est-ce qu'un Trait ?Implémenter des TraitsImplémentations par défautRedéfinir les comportements par défautTraits avec paramètresRécapitulatif - Lecteur multimédia