Wektory obiektów cech
Część sekcji Programowanie obiektowe ścieżki Rust w Coddy. Lekcja 46 z 61.
Widzisz już, jak Box<dyn Trait> pozwala jednej zmiennej przechowywać różne typy. Prawdziwa moc ujawnia się, gdy połączysz to z kolekcjami: przechowywaniem wielu różnych typów w jednej tablicy.
Zwykła tablica wymaga, aby wszystkie elementy były tego samego typu. Nie możesz mieć Vec<Dog>, który zawiera również koty. Jednak dzięki obiektom cech możesz utworzyć tablicę, która przechowuje wszystko, co implementuje wspólną cechę:
trait Speak {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
struct Bird;
impl Speak for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Speak for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
impl Speak for Bird {
fn speak(&self) -> String { String::from("Tweet!") }
}
fn main() {
let animals: Vec<Box<dyn Speak>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Bird),
];
}
Typ Vec<Box<dyn Speak>> oznacza „wektor obiektów cech opakowanych w pudełka”. Każdy element to Box wskazujący na coś, co implementuje Speak: rzeczywiste typy mogą się różnić. Każdą wartość opakowujesz za pomocą Box::new(), dodając ją do wektora.
Ten wzorzec jest niezbędny do tworzenia elastycznych systemów, w których trzeba zarządzać kolekcjami powiązanych, ale różnych obiektów, takich jak komponenty interfejsu użytkownika, obiekty w grze lub systemy wtyczek.
Wyzwanie
ŁatwyZbudujmy system powiadomień, który obsługuje różne typy alertów! Utworzysz kolekcję przechowującą różne typy powiadomień — e-maile, wiadomości SMS i powiadomienia push — wszystkie w jednym wektorze z użyciem obiektów cech.
Rozmieścisz swój kod w dwóch plikach:
notifications.rs: Zdefiniuj publiczną cechęNotifyz metodąsend, która przyjmuje&selfi zwraca wartość typuStringopisującą wysyłane powiadomienie. Następnie utwórz trzy publiczne struktury:Email: z publicznym polemrecipient(String). Metodasendpowinna zwracaćEmail to: {recipient}Sms: z publicznym polemphone(String). Metodasendpowinna zwracaćSMS to: {phone}Push: z publicznym polemdevice(String). Metodasendpowinna zwracaćPush to: {device}
main.rs: Dołącz moduł powiadomień i utwórz wektor typuVec<Box<dyn Notify>>, który przechowuje wszystkie trzy typy powiadomień. Użyj podanych danych wejściowych, aby utworzyć po jednym powiadomieniu każdego typu, dodaj je do wektora w tej kolejności (Email, Sms, Push), a następnie wypisz wynik wywołaniasend()dla pierwszego elementu wektora.
Kluczowe jest to, że mimo iż Email, Sms i Push to zupełnie różne struktury, mogą znajdować się razem w tym samym wektorze, ponieważ współdzielą cechę Notify. Każdy element jest opakowywany za pomocą Box::new(), aby utworzyć obiekt cechy.
Wynik powinien pokazywać pierwsze powiadomienie:
Email to: {recipient}Na przykład dla danych wejściowych alice@example.com, 555-1234 i iPhone-12:
Email to: alice@example.comOtrzymasz trzy dane wejściowe: adres e-mail odbiorcy, numer telefonu i nazwę urządzenia.
Spróbuj swoich sił
mod notifications;
use notifications::{Notify, Email, Sms, Push};
fn main() {
// Wczytaj dane wejściowe
let mut recipient = String::new();
std::io::stdin().read_line(&mut recipient).expect("Failed to read line");
let recipient = recipient.trim().to_string();
let mut phone = String::new();
std::io::stdin().read_line(&mut phone).expect("Failed to read line");
let phone = phone.trim().to_string();
let mut device = String::new();
std::io::stdin().read_line(&mut device).expect("Failed to read line");
let device = device.trim().to_string();
// TODO: Utwórz Vec<Box<dyn Notify>> do przechowywania różnych typów powiadomień
// TODO: Utwórz instancje Email, Sms i Push, używając danych wejściowych
// TODO: Dodaj je do wektora w kolejności (Email, Sms, Push), używając Box::new()
// TODO: Wypisz wynik wywołania send() na pierwszym elemencie
}
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