Abhängigkeitsinjektion (Dependency Injection) in Rust für Anfänger

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: Warum ist Dependency Injection in Rust wichtig?

\n\n

Beim Schreiben von Programmen in Rust werden Sie früher oder später auf die Situation stoßen, dass ein Modul (z. B. ein Service oder Controller) die Fähigkeiten eines anderen Moduls (Datenbank, API, Logger) nutzen muss. Der einfachste Weg ist, das benötigte Objekt direkt innerhalb des verwendenden Codes zu erstellen:

\n\n
struct UserService;\n\nimpl UserService {\n    fn get_user(&self, id: u32) -> String {\n        let db = Database::new(); // ❌ Feste Kopplung\n        db.query_user(id)\n    }\n}\n
\n\n

Dieser Ansatz erzeugt eine feste Kopplung (tight coupling). Der Code wird schwer zu testen, wiederzuverwenden und zu ändern. Dependency Injection (DI) ist ein Entwurfsmuster, bei dem Abhängigkeiten von außen an ein Objekt übergeben werden, anstatt innerhalb des Objekts erstellt zu werden. Rust, als Sprache mit einem Besitzsystem und Traits, bietet elegante Möglichkeiten, DI ohne Frameworks zu implementieren.

\n\n

In diesem Artikel werden wir anhand praktischer Beispiele untersuchen, wie man Abhängigkeiten in Rust injiziert: von der einfachen Übergabe von Strukturen bis hin zur Verwendung von Traits und intelligenten Zeigern.

\n\n

1. Grundlegender Ansatz: Abhängigkeiten über den Konstruktor übergeben

\n\n

Der einfachste Weg, eine Abhängigkeit in Rust zu injizieren, besteht darin, sie als Argument eines Konstruktors oder einer Funktion zu übergeben. Betrachten wir ein Beispiel mit einem Logger.

\n\n

Schlechtes Beispiel (ohne DI)

\n\n
struct ConsoleLogger;\n\nimpl ConsoleLogger {\n    fn log(&self, msg: &str) {\n        println!("[LOG]: {}", msg);\n    }\n}\n\nstruct OrderService {\n    logger: ConsoleLogger, // ❌ Feste Bindung an einen bestimmten Typ\n}\n\nimpl OrderService {\n    fn new() -> Self {\n        Self {\n            logger: ConsoleLogger, // Wird intern erstellt\n        }\n    }\n\n    fn place_order(&self, item: &str) {\n        self.logger.log(&format!("Bestellung: {}", item));\n    }\n}\n
\n\n

Gutes Beispiel (mit DI)

\n\n
struct ConsoleLogger;\n\nimpl ConsoleLogger {\n    fn log(&self, msg: &str) {\n        println!("[LOG]: {}", msg);\n    }\n}\n\nstruct OrderService {\n    logger: ConsoleLogger, // Abhängigkeit wird von außen übergeben\n}\n\nimpl OrderService {\n    // Konstruktor nimmt die Abhängigkeit entgegen\n    fn new(logger: ConsoleLogger) -> Self {\n        Self { logger }\n    }\n\n    fn place_order(&self, item: &str) {\n        self.logger.log(&format!("Bestellung: {}", item));\n    }\n}\n\nfn main() {\n    let logger = ConsoleLogger;\n    let service = OrderService::new(logger);\n    service.place_order("Rust-Buch");\n}\n
\n\n

Was hat sich geändert? Jetzt erstellt OrderService den Logger nicht selbst. Er erhält ihn fertig. Das ist bereits DI, aber es gibt eine Einschränkung: Wir sind immer noch an den konkreten Typ ConsoleLogger gebunden. Um den Code flexibel zu machen, müssen wir Traits verwenden.

\n\n

2. Verwendung von Traits zur Abstraktion von Abhängigkeiten

\n\n

Traits in Rust sind das Äquivalent zu Interfaces in anderen Sprachen. Sie ermöglichen es, einen Vertrag zu definieren, dem die Abhängigkeiten folgen müssen.

\n\n

Definieren des Logger-Traits

\n\n
trait Logger {\n    fn log(&self, msg: &str);\n}\n\nstruct ConsoleLogger;\nimpl Logger for ConsoleLogger {\n    fn log(&self, msg: &str) {\n        println!("[Konsole]: {}", msg);\n    }\n}\n\nstruct FileLogger {\n    filename: String,\n}\nimpl Logger for FileLogger {\n    fn log(&self, msg: &str) {\n        // Simulation des Schreibens in eine Datei\n        println!("[Datei {}]: {}", self.filename, msg);\n    }\n}\n
\n\n

Injizieren des Traits über Generics

\n\n
struct OrderService<L: Logger> {\n    logger: L, // Generischer Typ, der das Logger-Trait implementiert\n}\n\nimpl<L: Logger> OrderService<L> {\n    fn new(logger: L) -> Self {\n        Self { logger }\n    }\n\n    fn place_order(&self, item: &str) {\n        self.logger.log(&format!("Bestellung: {}", item));\n    }\n}\n\nfn main() {\n    let console_logge                

Blogs

Buchempfehlungen