Iterieren über Trait-Objekte
Teil des Abschnitts Object Oriented Programming der Rust-Journey von Coddy — Lektion 47 von 61.
Da Sie nun verschiedene Typen in einem Vec<Box<dyn Trait>> speichern können, ist der nächste Schritt die Verarbeitung dieser Sammlung. Das Iterieren über Trait-Objekte funktioniert genau wie das Iterieren über jeden anderen Vektor – Sie durchlaufen jedes Element in einer Schleife und rufen die durch den Trait definierten Methoden auf.
trait Shape {
fn area(&self) -> f64;
}
struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }
impl Shape for Circle {
fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}
impl Shape for Rectangle {
fn area(&self) -> f64 { self.width * self.height }
}
fn main() {
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 2.0 }),
Box::new(Rectangle { width: 3.0, height: 4.0 }),
Box::new(Circle { radius: 1.0 }),
];
for shape in &shapes {
println!("Area: {}", shape.area());
}
}
Wenn Sie mit for shape in &shapes iterieren, ist jedes shape eine Referenz auf ein Box<dyn Shape>. Sie können jede Methode des Shape-Traits direkt aufrufen – Rust übernimmt den dynamischen Dispatch automatisch und ruft die korrekte Implementierung für jeden konkreten Typ auf.
Dieses Muster ist leistungsstark für das Aggregieren von Ergebnissen. Sie könnten alle Flächen summieren, Formen nach Größe filtern oder die Sammlung transformieren. Die entscheidende Erkenntnis ist, dass Sie, sobald sich Elemente in einer Trait-Objekt-Sammlung befinden, rein über die Schnittstelle des Traits mit ihnen interagieren, unabhängig von ihren zugrunde liegenden Typen.
Aufgabe
EinfachLassen Sie uns einen Preisrechner für einen Warenkorb erstellen, der verschiedene Arten von Artikeln enthalten kann! Sie werden ein System erstellen, in dem Produkte und Dienstleistungen – jeweils mit unterschiedlicher Preislogik – zusammen gespeichert und verarbeitet werden können, um einen Gesamtpreis zu berechnen.
Sie werden Ihren Code auf zwei Dateien verteilen:
items.rs: Definieren Sie ein öffentlichesPriceable-Trait mit einerprice-Methode, die&selfentgegennimmt und einf64zurückgibt. Erstellen Sie dann zwei öffentliche Structs:Product— mit den öffentlichen Feldernname(String) undcost(f64). Die Methodepricesollte einfach dencostzurückgeben.Service— mit den öffentlichen Feldernname(String),hourly_rate(f64) undhours(f64). Die Methodepricesolltehourly_rate * hourszurückgeben.
main.rs: Binden Sie Ihr items-Modul ein und erstellen Sie einen Vektor vom TypVec<Box<dyn Priceable>>. Verwenden Sie die Eingaben, um ein Product und ein Service zu erstellen, fügen Sie diese Ihrem Vektor hinzu und iterieren Sie dann durch die Sammlung, um den Gesamtpreis aller Artikel zu berechnen und auszugeben.
Die Stärke von Trait-Objekten zeigt sich hier – Ihr Iterationscode muss nicht wissen, ob er ein Product oder ein Service verarbeitet. Er ruft einfach price() für jedes Element auf, und Rust leitet den Aufruf automatisch an die richtige Implementierung weiter.
Ihre Ausgabe sollte den Gesamtpreis mit einer Dezimalstelle anzeigen:
Total: ${total}Zum Beispiel mit den Eingaben Laptop, 999.99, Consulting, 150.0 und 3.0:
Total: $1449.99Und mit den Eingaben Book, 29.99, Tutoring, 50.0 und 2.0:
Total: $129.99Sie erhalten fünf Eingaben: den Produktnamen, die Produktkosten, den Dienstleistungsnamen, den Stundensatz und die geleisteten Arbeitsstunden (parsen Sie numerische Werte als f64).
Spickzettel
Um über eine Sammlung von Trait-Objekten zu iterieren, verwenden Sie eine standardmäßige for-Schleife mit einer Referenz auf den Vektor:
for item in &collection {
item.trait_method();
}
Beim Iterieren mit for item in &vec ist jedes item eine Referenz auf ein Box<dyn Trait>. Sie können jede Methode des Traits direkt aufrufen – Rust übernimmt den dynamischen Dispatch automatisch und ruft die korrekte Implementierung für jeden konkreten Typ auf.
Beispiel für das Iterieren über Trait-Objekte, um Ergebnisse zu aggregieren:
trait Shape {
fn area(&self) -> f64;
}
struct Circle { radius: f64 }
struct Rectangle { width: f64, height: f64 }
impl Shape for Circle {
fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }
}
impl Shape for Rectangle {
fn area(&self) -> f64 { self.width * self.height }
}
fn main() {
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 2.0 }),
Box::new(Rectangle { width: 3.0, height: 4.0 }),
];
for shape in &shapes {
println!("Area: {}", shape.area());
}
}
Dieses Muster ist nützlich, um Ergebnisse zu aggregieren – Sie können Werte summieren, nach Kriterien filtern oder die Sammlung transformieren, während Sie mit den Elementen rein über die Schnittstelle des Traits interagieren.
Probier es selbst
mod items;
use items::{Priceable, Product, Service};
fn main() {
// Eingaben lesen
let mut product_name = String::new();
std::io::stdin().read_line(&mut product_name).expect("Failed to read line");
let product_name = product_name.trim().to_string();
let mut product_cost = String::new();
std::io::stdin().read_line(&mut product_cost).expect("Failed to read line");
let product_cost: f64 = product_cost.trim().parse().expect("Failed to parse");
let mut service_name = String::new();
std::io::stdin().read_line(&mut service_name).expect("Failed to read line");
let service_name = service_name.trim().to_string();
let mut hourly_rate = String::new();
std::io::stdin().read_line(&mut hourly_rate).expect("Failed to read line");
let hourly_rate: f64 = hourly_rate.trim().parse().expect("Failed to parse");
let mut hours = String::new();
std::io::stdin().read_line(&mut hours).expect("Failed to read line");
let hours: f64 = hours.trim().parse().expect("Failed to parse");
// TODO: Erstelle ein Product unter Verwendung von product_name und product_cost
// TODO: Erstelle einen Service unter Verwendung von service_name, hourly_rate und hours
// TODO: Erstelle einen Vec<Box<dyn Priceable>> und füge beide Elemente hinzu
// TODO: Iteriere durch den Vektor und berechne den Gesamtpreis
// TODO: Gib die Gesamtsumme mit einer Nachkommastelle aus
// Format: Total: ${total:.1}
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Object Oriented Programming
1Methoden und Verhalten
Einführung in ImplementierungsblöckeDer Self-ParameterVeränderliche MethodenAssoziierte FunktionenMehrere ImplementierungsblöckeMethod ChainingRückblick – Rechteck-Aktionen4Projekt: Virtuelles Haustier
Das Haustier definierenDas Haustier füttern7Standard-Traits
Das Debug-TraitDas Display-TraitClone und CopyGleichheits-TraitsZusammenfassung – Druckbarer Punkt10Projekt: Dokumentensystem
Das Draw-TraitText-Komponente2Kapselung und Module
Grundlagen der ModuleDas Public-SchlüsselwortPrivate FelderGetterSetterWiederholung – Sicherer Tresor5Generics
Generische StructsGenerische MethodenMehrere generische TypenGenerische FunktionenZusammenfassung - Koordinatenpunkt8Traits als Einschränkungen
Syntax für Trait BoundsMehrfache BoundsDie Where-KlauselTypen mit Traits zurückgebenRückblick – Generischer Drucker11Design Patterns in Rust
Newtype-PatternKompositionDas Drop-TraitFrom und IntoZusammenfassung – Smart Pointer Mock3Fortgeschrittene Enums
Enums mit DatenMethoden für EnumsMatching von DatenvariantenDas Option-Enum erneut betrachtetZusammenfassung – Shape-Enum6Definition von Traits
Was ist ein Trait?Traits implementierenStandardimplementierungenStandardimplementierungen überschreibenTraits mit ParameternZusammenfassung – Media Player9Trait-Objekte
Dynamischer DispatchVektoren von TraitsIterieren über Trait-ObjekteRückblick – Zoo-Manager