Menu
Coddy logo textTech

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.

challenge icon

Défi

Facile

Construisons 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 publique CPU avec deux champs publics : cores (u32) pour le nombre de cœurs, et speed_ghz (f32) pour la vitesse d'horloge. Ajoutez une méthode specs qui retourne une String décrivant le CPU au format "{cores}-core @ {speed_ghz}GHz".
  • memory.rs : Définissez une structure publique Memory avec un champ public size_gb (u32) pour la taille de la mémoire. Ajoutez une méthode specs qui retourne une String au format "{size_gb}GB RAM".
  • main.rs : Importez les deux modules et créez une structure publique Computer qui possède un champ cpu de type CPU et un champ memory de type Memory. Implémentez une méthode system_info sur 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 RAM

Et avec les entrées 4, 2.8, et 32 :

System: 4-core @ 2.8GHz | 32GB RAM

Vous 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
}
quiz iconTestez-vous

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