Entwurfsmuster in Rust: Fortgeschrittenes Niveau mit Beispielen
\n\nEntwurfsmuster sind bewährte Lösungen für typische Aufgaben, die helfen, eine flexible, wartbare und skalierbare Architektur zu erstellen. In Rust, mit seinem System der Eigentümerschaft, Ausleihe und Traits, erhalten viele klassische Entwurfsmuster eine einzigartige Implementierung. In diesem Artikel werden wir fortgeschrittene Entwurfsmuster behandeln, die im Rust-Ökosystem besonders effektiv sind.
\n\n1. Fortgeschrittener Builder
\n\nDer klassische Builder-Pattern wird in Rust oft durch eine Methodenkette implementiert, die &mut Self zurückgibt. Auf fortgeschrittenem Niveau verwenden wir jedoch einen typisierten Builder mit Zustandstypen, um die Korrektheit bereits zur Kompilierzeit sicherzustellen.
Problem: Kontrolle der Pflichtfelder
\nEin gewöhnlicher Builder garantiert nicht, dass der Benutzer alle Pflichtfelder ausgefüllt hat. Wir könnten vergessen, .build() aufzurufen oder einen kritischen Parameter nicht zu setzen.
Lösung: Builder mit Phantom-Typen
\nuse std::marker::PhantomData;\n\n// Zustandstypen für Felder\nstruct UsernameNotSet;\nstruct UsernameSet;\nstruct PasswordNotSet;\nstruct PasswordSet;\n\nstruct UserBuilder<U, P> {\n username: Option<String>,\n password: Option<String>,\n _username: PhantomData<U>,\n _password: PhantomData<P>,\n}\n\nimpl UserBuilder<UsernameNotSet, PasswordNotSet> {\n pub fn new() -> Self {\n Self {\n username: None,\n password: None,\n _username: PhantomData,\n _password: PhantomData,\n }\n }\n}\n\nimpl<P> UserBuilder<UsernameNotSet, P> {\n pub fn username(mut self, name: &str) -> UserBuilder<UsernameSet, P> {\n self.username = Some(name.to_string());\n UserBuilder {\n username: self.username,\n password: self.password,\n _username: PhantomData,\n _password: PhantomData,\n }\n }\n}\n\nimpl<U> UserBuilder<U, PasswordNotSet> {\n pub fn password(mut self, pwd: &str) -> UserBuilder<U, PasswordSet> {\n self.password = Some(pwd.to_string());\n UserBuilder {\n username: self.username,\n password: self.password,\n _username: PhantomData,\n _password: PhantomData,\n }\n }\n}\n\nimpl UserBuilder<UsernameSet, PasswordSet> {\n pub fn build(self) -> User {\n User {\n username: self.username.expect("Username must be set"),\n password: self.password.expect("Password must be set"),\n }\n }\n}\n\npub struct User {\n pub username: String,\n pub password: String,\n}\n\nfn main() {\n // Jetzt verhindert der Compiler den Aufruf von build(), ohne beide Felder gesetzt zu haben\n let user = UserBuilder::new()\n .username("alice")\n .password("secret123")\n .build();\n println!("User: {}", user.username);\n}\n\nVorteil: Der Fehler "Feld vergessen" wird bereits zur Kompilierzeit erkannt, nicht erst zur Laufzeit.
\n\n2. State-Pattern über Traits
\n\nDas State-Pattern ermöglicht es einem Objekt, sein Verhalten bei Änderung des internen Zustands zu ändern. In Rust wird dies elegant durch Enumerationen (enum) und Traits implementiert.
\n\nImplementierung mit enum
\nenum ConnectionState {\n Disconnected,\n Connecting,\n Connected,\n}\n\nstruct Connection {\n state: ConnectionState,\n}\n\nimpl Connection {\n pub fn new() -> Self {\n Self { state: ConnectionState::Disconnected }\n }\n\n pub fn connect(&mut self) {\n match self.state {\n ConnectionState::Disconnected => {\n println!("Beginne Verbindung...");\n self.state = ConnectionState::Connecting;\n }\n ConnectionState::Connecting => {\n printl