Iterowanie po obiektach cech
Część sekcji Programowanie obiektowe ścieżki Rust w Coddy. Lekcja 47 z 61.
Skoro potrafisz już przechowywać różne typy w Vec<Box<dyn Trait>>, kolejnym krokiem jest przetwarzanie tej kolekcji. Iterowanie po obiektach cech działa tak samo jak iterowanie po dowolnej tablicy. Przechodzisz przez każdy element i wywołujesz metody zdefiniowane przez cechę.
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());
}
}
Gdy iterujesz za pomocą for shape in &shapes, każdy shape jest referencją do Box<dyn Shape>. Możesz bezpośrednio wywołać dowolną metodę z cechy Shape: Rust automatycznie obsługuje dynamiczne wywoływanie metod, wywołując właściwą implementację dla każdego konkretnego typu.
Ten wzorzec jest przydatny do agregowania wyników. Możesz zsumować wszystkie pola, filtrować kształty według rozmiaru lub przekształcić kolekcję. Kluczowy wniosek jest taki, że gdy elementy znajdą się w kolekcji obiektów cechy, korzystasz z nich wyłącznie za pośrednictwem interfejsu tej cechy, niezależnie od ich typów bazowych.
Wyzwanie
ŁatwyZbudujmy kalkulator cen do koszyka zakupowego, który może zawierać różne rodzaje produktów! Stworzysz system, w którym produkty i usługi, z których każdy ma inną logikę ustalania ceny, można przechowywać razem i przetwarzać w celu obliczenia łącznej ceny.
Rozmieścisz swój kod w dwóch plikach:
items.rs: Zdefiniuj publiczny traitPriceablez metodąprice, która przyjmuje&selfi zwracaf64. Następnie utwórz dwie publiczne struktury:Product: z publicznymi polaminame(String) icost(f64). Metodapricepowinna po prostu zwracać wartośćcost.Service: z publicznymi polaminame(String),hourly_rate(f64) ihours(f64). Metodapricepowinna zwracać wartośćhourly_rate * hours.
main.rs: Dołącz moduł items i utwórz wektor typuVec<Box<dyn Priceable>>. Użyj danych wejściowych, aby utworzyć jeden produkt i jedną usługę, dodaj je do wektora, a następnie przeiteruj po kolekcji, aby obliczyć i wyświetlić łączną cenę wszystkich elementów.
Właśnie tutaj widać zalety obiektów traitów. Twój kod iterujący nie musi wiedzieć, czy przetwarza produkt, czy usługę. Po prostu wywołuje price() dla każdego elementu, a Rust automatycznie wybiera właściwą implementację.
Wynik powinien wyświetlać łączną cenę z dwoma miejscami po przecinku:
Total: ${total}Na przykład dla danych wejściowych Laptop, 999.99, Consulting, 150.0 i 3.0:
Total: $1449.99Oraz dla danych wejściowych Book, 29.99, Tutoring, 50.0 i 2.0:
Total: $129.99Otrzymasz pięć danych wejściowych: nazwę produktu, cenę produktu, nazwę usługi, stawkę godzinową i liczbę przepracowanych godzin (wartości liczbowe sparsuj jako f64).
Spróbuj swoich sił
mod items;
use items::{Priceable, Product, Service};
fn main() {
// Wczytaj dane wejściowe
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: Utwórz Product, używając product_name i product_cost
// TODO: Utwórz Service, używając service_name, hourly_rate i hours
// TODO: Utwórz Vec<Box<dyn Priceable>> i dodaj do niego oba elementy
// TODO: Przejdź przez wektor i oblicz łączną cenę
// TODO: Wyświetl sumę z jednym miejscem po przecinku
// Format: Total: ${total:.1}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Metody i zachowanie
Wprowadzenie do bloków implementacjiParametr selfMetody modyfikująceFunkcje powiązaneWiele bloków implementacjiŁańcuchowe wywoływanie metodPowtórzenie — działania na prostokącie4Projekt: Wirtualne zwierzątko
Definiowanie zwierzątkaKarmienie zwierzątka7Standardowe cechy
Cecha DebugCecha DisplayClone i CopyCechy równościPowtórka – punkt do wydrukowania10Projekt: System dokumentów
Trait DrawKomponent tekstowy2Hermetyzacja i moduły
Podstawy modułówSłowo kluczowe publicPola prywatneGetterySetteryPodsumowanie — bezpieczna skrytka5Typy generyczne
Struktury generyczneMetody generyczneWiele typów generycznychFunkcje generycznePodsumowanie – punkt współrzędnych8Cechy jako ograniczenia
Składnia ograniczeń cechWiele ograniczeńKlauzula whereZwracanie typów z użyciem cechPodsumowanie — generyczna drukarka11Wzorce projektowe w Rust
Wzorzec NewtypeKompozycjaTrait DropFrom i IntoPodsumowanie — atrapowy inteligentny wskaźnik3Zaawansowane typy wyliczeniowe
Typy wyliczeniowe z danymiMetody typów wyliczeniowychDopasowywanie wariantów danychTyp wyliczeniowy Option — powtórzeniePodsumowanie — typ wyliczeniowy Shape6Definiowanie cech
Czym jest cecha?Implementowanie cechImplementacje domyślneNadpisywanie wartości domyślnychCechy z parametramiPowtórzenie — odtwarzacz multimedialny9Obiekty cech
Dynamiczne wywołanieWektory obiektów cechIterowanie po obiektach cechPodsumowanie – zarządzanie zooPoćwicz samodzielnie: Kompilator Rust online