Principes SOLID en Rust : théorie et pratique

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 : Pourquoi SOLID est important pour les développeurs Rust



SOLID est un ensemble de cinq principes fondamentaux de conception orientée objet qui aident à créer un code flexible, maintenable et évolutif. Bien que Rust ne soit pas un langage OOP classique, ces principes s'appliquent parfaitement grâce au système de traits, de possession et de génériques. Dans cet article, nous allons décomposer chaque principe SOLID avec des exemples pratiques en Rust.



Vous apprendrez comment :

  • Éviter les structures "divines" grâce à la décomposition
  • Concevoir des systèmes extensibles via les traits
  • Utiliser efficacement le polymorphisme sans héritage
  • Séparer les interfaces et injecter les dépendances


1. Single Responsibility Principle (SRP) — Principe de responsabilité unique



Le SRP stipule : "Une structure ou un module ne doit avoir qu'une seule raison de changer". En Rust, cela signifie que chaque type doit être responsable d'une tâche clairement définie.



Violation du SRP : Structure "divine"



struct Report {    data: Vec<String>,}

impl Report { fn generate(&self) -> String { self.data.join(", ") }

fn save_to_file(&self, path: &str) -> std::io::Result<()> { let content = self.generate(); std::fs::write(path, content) }

fn send_email(&self, recipient: &str) { // Logique d'envoi d'email println!("Envoi du rapport {} à {}", self.generate(), recipient); }}


Ce code viole le SRP : la structure Report est responsable de la génération des données, de leur sauvegarde dans un fichier et de l'envoi d'email. Modifier la logique de sauvegarde ou d'envoi nécessitera de modifier le rapport lui-même.



Implémentation correcte avec le SRP



struct Report {    data: Vec<String>,}

impl Report { fn new(data: Vec<String>) -> Self { Self { data } }

fn generate(&self) -> String { self.data.join(", ") }}

struct FileSaver;

impl FileSaver { fn save(report: &Report, path: &str) -> std::io::Result<()> { std::fs::write(path, report.generate()) }}

struct EmailSender;

impl EmailSender { fn send(report: &Report, recipient: &str) { println!("Envoi du rapport {} à {}", report.generate(), recipient); }}


Maintenant, chaque structure a une seule responsabilité : Report — la génération, FileSaver — la sauvegarde, EmailSender — l'envoi. Cela simplifie les tests et la modification du code.



2. Open/Closed Principle (OCP) — Principe d'ouverture/fermeture



L'OCP affirme : "Les classes doivent être ouvertes à l'extension, mais fermées à la modification". En Rust, cela est réalisé grâce aux traits et aux génériques.



Exemple : Système de traitement des paiements



trait PaymentProcessor {    fn process(&self, amount: f64) -> String;}

struct CreditCardProcessor;

impl PaymentProcessor for CreditCardProcessor { fn process(&self, amount: f64) -> String { format!("Traitement de {} par carte de crédit", amount) }}

struct PayPalProcessor;

impl PaymentProcessor for PayPalProcessor { fn process(&self, amount: f64) -> String { format!("Traitement de {} par PayPal", amount) }}

struct PaymentHandler<T: PaymentProcessor> { processor: T,}

impl<T: PaymentProcessor> PaymentHandler<T> { fn execute(&self, amount: f64) -> String { self.processor.process(amount) }}


L'ajout d'un nouveau mode de paiement (par exemple, CryptoProcessor) ne nécessite pas de modifier le code existant — il suffit d'implémenter le trait PaymentProcessor.



Blogs

Book Recommendations