Menu
Coddy logo textTech

Векторы трейтов

Часть раздела Объектно-ориентированное программирование путешествия по 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().

Этот шаблон необходим для создания гибких систем, в которых нужно управлять коллекциями связанных, но разных объектов, например компонентов пользовательского интерфейса, игровых сущностей или систем плагинов.

challenge icon

Задание

Легко

Давайте создадим систему уведомлений, способную обрабатывать разные типы оповещений! Вы создадите коллекцию, хранящую различные типы уведомлений: электронные письма, 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() на первом элементе
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Rust