SOLID-Prinzipien in Rust: Theorie und Praxis für Entwickler

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: SOLID-Prinzipien in Rust — Warum ist das nötig?

SOLID ist eine Sammlung von fünf grundlegenden Prinzipien der objektorientierten Programmierung, die helfen, flexiblen, wartbaren und skalierbaren Code zu erstellen. Obwohl Rust keine klassische OOP-Sprache ist (es hat keine Klassenvererbung im üblichen Sinne), ermöglichen sein Ownership-System, Traits und Generics eine hervorragende Umsetzung aller SOLID-Prinzipien. Das Verständnis dieser Prinzipien im Kontext von Rust hilft, Code zu schreiben, der leicht zu testen, umzugestalten und zu erweitern ist.

In diesem Artikel analysieren wir jedes der fünf SOLID-Prinzipien mit praktischen Beispielen in Rust. Sie werden sehen, wie trait Interfaces ersetzt, wie Box<dyn Trait> bei der Abhängigkeitsinversion hilft und wie man häufige Fehler vermeidet.



1. Single-Responsibility-Prinzip (SRP) in Rust

Single-Responsibility-Prinzip (SRP): Jedes Modul, Struct oder jede Funktion sollte nur einen Grund für eine Änderung haben. In Rust bedeutet dies, dass ein Struct für eine einzelne Entität verantwortlich sein sollte, anstatt Geschäftslogik, Datenverarbeitung und Ausgabe zu vermischen.



Beispiel einer SRP-Verletzung

struct User {    name: String,    email: String,}

impl User { // Vermischung von Geschäftslogik und Persistenz fn save_to_db(&self) { println!("Speichere {} in der DB", self.name); } fn send_email(&self, message: &str) { println!("Sende E-Mail an {}: {}", self.email, message); }}

Hier ist das User-Struct verantwortlich für Benutzerdaten, Persistenz und das Senden von E-Mails. Dies verstößt gegen das SRP.



Korrekte SRP-Implementierung

struct User {    name: String,    email: String,}

struct UserRepository;impl UserRepository { fn save(&self, user: &User) { println!("Speichere {} in der DB", user.name); }}

struct EmailService;impl EmailService { fn send(&self, user: &User, message: &str) { println!("Sende E-Mail an {}: {}", user.email, message); }}

Jetzt hat jedes Struct eine einzige Verantwortung: User — Daten, UserRepository — Persistenz, EmailService — Versand. Der Code ist modularer und testbarer geworden.



2. Open/Closed-Prinzip (OCP) mit Traits

Open/Closed-Prinzip (OCP): Software-Entitäten sollten offen für Erweiterung, aber geschlossen für Modifikation sein. In Rust wird dies idealerweise mit trait und dynamischem Dispatch umgesetzt.



Beispiel ohne OCP

fn calculate_area(shape: &str, params: &[f64]) -> f64 {    match shape {        "circle" => std::f64::consts::PI * params[0] * params[0],        "rectangle" => params[0] * params[1],        _ => panic!("Unbekannte Form"),    }}

Beim Hinzufügen einer neuen Form (z. B. Dreieck) muss die Funktion modifiziert werden.



Korrekter Ansatz mit OCP

trait Shape {    fn area(&self) -> f64;}

struct Circle { radius: f64 }impl Shape for Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }}

struct Rectangle { width: f64, height: f64 }impl Shape for Rectangle { fn area(&self) -> f64 { self.width * self.height }}

// Funktion ist geschlossen für Modifikation, aber offen für Erweiterungfn print_area(shape: &dyn Shape) { println!("Fläche: {}", shape.area());}

Um ein Dreieck hinzuzufügen, muss man jetzt nur ein neues Struct erstellen und das trait Shape implementieren. Die print_area-Funktion benötigt keine Änderungen.



3. Liskovsches Substitutionsprinzip (LSP) und Vererbung mit Traits

Liskovsches Substitutionsprinzip (LSP): Objekte in einem Programm sollten durch Instanzen von

Blogs

Buchempfehlungen