Principes SOLID en Rust : théorie et pratique (niveau avancé)

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : SOLID dans le monde de Rust

\n\n

Les principes SOLID sont le fondement d'une architecture flexible et maintenable. Cependant, leur implémentation en Rust a ses spécificités, dues au système unique de possession, aux traits et à l'absence d'OO classique. Dans cet article, nous analyserons comment appliquer chacun des cinq principes à un niveau avancé, en utilisant un Rust idiomatique. Nous ne nous contenterons pas d'énumérer les règles, mais montrerons comment elles se transforment dans le contexte du système de types et de l'emprunt.

\n\n

Cet article est destiné aux développeurs déjà familiarisés avec les bases de Rust et de SOLID. Nous nous concentrerons sur les subtilités : comment éviter de violer le LSP lors du travail avec des types génériques, comment le ISP influence la conception des traits, et pourquoi le DIP en Rust nécessite souvent l'utilisation d'objets traits ou de Box.

\n\n

1. Principe de responsabilité unique (SRP) et possession

\n\n

En Rust, le SRP prend une dimension supplémentaire en raison des règles strictes de possession. Une classe (ou structure) ne doit avoir qu'une seule raison de changer. Mais comment cela se rapporte-t-il au transfert de possession ?

\n\n

Problème : structure gérant les ressources et la logique

\n

Considérons un exemple où une structure gère à la fois un descripteur de fichier et analyse les données. Cela viole le SRP.

\n\n
use std::fs::File;\nuse std::io::Read;\n\n// ❌ Violation du SRP : la structure gère à la fois la ressource et traite les données\nstruct DataProcessor {\n    file: File,\n}\n\nimpl DataProcessor {\n    fn new(path: &str) -> Self {\n        DataProcessor {\n            file: File::open(path).unwrap(),\n        }\n    }\n\n    fn process(&mut self) -> Vec<u8> {\n        let mut buffer = Vec::new();\n        self.file.read_to_end(&mut buffer).unwrap();\n        // ... logique de traitement complexe\n        buffer\n    }\n}\n
\n\n

Solution : séparation via le transfert de possession

\n

Séparons les responsabilités : une structure est responsable de la lecture, une autre du traitement. La possession est transférée explicitement.

\n\n
use std::fs::File;\nuse std::io::Read;\n\n// ✅ Première responsabilité : gestion du fichier\nstruct FileReader {\n    file: File,\n}\n\nimpl FileReader {\n    fn new(path: &str) -> Self {\n        FileReader {\n            file: File::open(path).unwrap(),\n        }\n    }\n\n    fn read(self) -> Vec<u8> {\n        let mut buffer = Vec::new();\n        // self.file est consommée (déplacée), car après la lecture elle n'est plus nécessaire\n        self.file.read_to_end(&mut buffer).unwrap();\n        buffer\n    }\n}\n\n// ✅ Deuxième responsabilité : traitement des données\nstruct DataProcessor {\n    data: Vec<u8>,\n}\n\nimpl DataProcessor {\n    fn new(data: Vec<u8>) -> Self {\n        DataProcessor { data }\n    }\n\n    fn process(&self) -> Vec<u8> {\n        // Traitement immuable\n        self.data.iter().map(|&b| b + 1).collect()\n    }\n}\n\nfn main() {\n    let reader = FileReader::new("data.bin");\n    let raw_data = reader.read(); // reader n'est plus accessible\n    let processor = DataProcessor::new(raw_data);\n    let result = processor.process();\n}\n
\n\n

Point clé : nous avons utilisé self (possession) au lieu de &self dans read(), pour indiquer explicitement qu'après la lecture, FileReader n'est plus nécessaire. Cela rend le flux de données transparent et empêche les fuites de ressources.

\n\n

2. Principe d'ouverture/fermeture (OCP) et traits

\n\n

L'OCP stipule : les entités doivent être ouvertes à l'extension, mais fermées à la modification. En Rust, cela est idéalement implémenté via les traits et les génériques sans utiliser de tables virtuelles (vtable), si cela n'est pas nécessaire.

\n\n

Exemple avancé : stratégie avec zéro surcoût

\n

Supposons que nous ayons un système de journalisation. Au lieu de modifier le noyau de l'application pour chaque nouveau backend, nous utilisons le polymorphisme via les traits.

\n\n
/                

Blogs

Book Recommendations