Векторы трейтов
Часть раздела Объектно-ориентированное программирование путешествия по Rust на Coddy. Урок 46 из 61.
Вы видели, как Box<dyn Trait> позволяет одной переменной хранить разные типы. Настоящая сила проявляется, когда вы объединяете это с коллекциями: храните несколько разных типов в одном векторе.
Обычный вектор требует, чтобы все элементы имели один и тот же тип. Нельзя иметь Vec<Dog>, который также содержит кошек. Но с объектами трейтов можно создать вектор, который содержит что угодно, реализующее общий трейт:
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),
];
}
Тип Vec<Box<dyn Speak>> означает «вектор упакованных объектов трейтов». Каждый element — это Box, указывающий на нечто, что реализует Speak: фактические типы могут быть different. При добавлении каждого значения в вектор вы оборачиваете его с помощью Box::new().
Этот шаблон необходим для создания гибких систем, в которых нужно управлять коллекциями связанных, но разных объектов, например компонентов пользовательского интерфейса, игровых сущностей или систем плагинов.
Задание
ЛегкоДавайте создадим систему уведомлений, способную обрабатывать разные типы оповещений! Вы создадите коллекцию, хранящую различные типы уведомлений: электронные письма, SMS-сообщения и push-уведомления — всё в одном векторе с использованием объектов трейтов.
Вы организуете код в двух файлах:
notifications.rs: Определите публичный трейтNotifyс методомsend, который принимает&selfи возвращаетString, описывающую отправляемое уведомление. Затем создайте три публичные структуры:Email: с публичным полемrecipient(String). Методsendдолжен возвращатьEmail to: {recipient}Sms: с публичным полемphone(String). Методsendдолжен возвращатьSMS to: {phone}Push: с публичным полемdevice(String). Методsendдолжен возвращатьPush to: {device}
main.rs: Подключите модуль уведомлений и создайте вектор типаVec<Box<dyn Notify>>, содержащий все три типа уведомлений. Используйте предоставленные входные данные, чтобы создать по одному уведомлению каждого типа, добавьте их в вектор по порядку (Email, Sms, Push), а затем выведите результат вызоваsend()для первого элемента вектора.
Ключевая идея заключается в том, что, несмотря на то что Email, Sms и Push являются совершенно разными структурами, они могут находиться в одном векторе, поскольку используют общий трейт Notify. Каждый элемент оборачивается с помощью Box::new() для создания объекта трейта.
В результате должно отображаться первое уведомление:
Email to: {recipient}Например, если ввести alice@example.com, 555-1234 и iPhone-12:
Email to: alice@example.comВы получите три входных значения: адрес получателя электронной почты, номер телефона и имя устройства.
Попробуйте сами
mod notifications;
use notifications::{Notify, Email, Sms, Push};
fn main() {
// Читаем входные данные
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: Создать Vec<Box<dyn Notify>> для хранения различных типов уведомлений
// TODO: Создать экземпляры Email, Sms и Push, используя входные данные
// TODO: Добавить их в вектор в порядке (Email, Sms, Push), используя Box::new()
// TODO: Вывести результат вызова send() на первом элементе
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Методы и поведение
Введение в блоки реализацииПараметр SelfИзменяемые методыАссоциированные функцииНесколько блоков реализацииЦепочки вызовов методовИтоги: действия с Rectangle4Проект: Виртуальный питомец
Определение питомцаКормление питомца2Инкапсуляция и модули
Основы модулейКлючевое слово PublicПриватные поляГеттерыСеттерыИтоги — Безопасный сейф5Обобщения
Обобщённые структурыОбобщённые методыНесколько обобщённых типовОбобщённые функцииПовторение — Координатная точка8Трейты как ограничения
Синтаксис ограничений трейтовМножественные ограниченияКонструкция whereВозврат типов с трейтамиИтоги — Обобщённый принтер11Паттерны проектирования в Rust
Паттерн NewtypeКомпозицияТрейт DropFrom и IntoИтоги — Мок-объект Smart Pointer3Продвинутые перечисления
Перечисления с даннымиМетоды в перечисленияхСопоставление вариантов с даннымиСнова об Option EnumИтоги — перечисление Shape6Определение трейтов
Что такое трейт?Реализация трейтовРеализации по умолчаниюПереопределение реализаций по умолчаниюТрейты с параметрамиИтоги — Media Player9Трейт-объекты
Динамическая диспетчеризацияВекторы трейтовИтерация по трейт-объектамПовторение — Менеджер зоопаркаПотренируйтесь самостоятельно: Онлайн-компилятор Rust