Menu
Coddy logo textTech

Итерация по трейт-объектам

Часть раздела Объектно-ориентированное программирование путешествия по Rust на Coddy. Урок 47 из 61.

Теперь, когда вы умеете хранить разные типы в Vec<Box<dyn Trait>>, следующий шаг — обработка этой коллекции. Итерация по объектам трейтов работает так же, как итерация по любому вектору. Вы проходите по каждому элементу и вызываете методы, определённые трейтом.

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());
    }
}

Когда вы перебираете элементы с помощью for shape in &shapes, каждый shape является ссылкой на Box<dyn Shape>. Вы можете напрямую вызвать любой метод трейта Shape: Rust автоматически обрабатывает динамическую диспетчеризацию, вызывая правильную реализацию для каждого конкретного типа.

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

challenge icon

Задание

Легко

Давайте создадим калькулятор стоимости для корзины покупок, которая может содержать разные типы товаров! Вы создадите систему, в которой товары и услуги, каждый со своей логикой расчёта цены, можно хранить вместе и обрабатывать для вычисления общей стоимости.

Вы организуете код в двух файлах:

  • items.rs: Define публичный трейт Priceable с методом price, который принимает &self и возвращает значение типа f64. Затем создайте две публичные структуры:
    • Product: с публичными полями name (String) и cost (f64). Метод price должен просто возвращать значение cost.
    • Service: с публичными полями name (String), hourly_rate (f64) и hours (f64). Метод price должен возвращать значение hourly_rate * hours.
  • main.rs: Подключите модуль items и создайте вектор типа Vec<Box<dyn Priceable>>. Используйте входные данные, чтобы создать один Product и одну Service, добавьте их в вектор, затем выполните итерацию по коллекции, чтобы вычислить и вывести общую стоимость всех элементов.

Здесь особенно проявляется сила объектов трейтов. Вашему коду итерации не нужно знать, обрабатывает ли он Product или Service. Он просто вызывает price() для каждого элемента, а Rust автоматически выбирает правильную реализацию.

В выводе общая стоимость должна отображаться с двумя знаками после десятичной точки:

Total: ${total}

Например, для входных данных Laptop, 999.99, Consulting, 150.0 и 3.0:

Total: $1449.99

А для входных данных Book, 29.99, Tutoring, 50.0 и 2.0:

Total: $129.99

Вы получите пять входных данных: название товара, стоимость товара, название услуги, почасовую ставку и количество отработанных часов (числовые значения необходимо разобрать как f64).

Попробуйте сами

mod items;

use items::{Priceable, Product, Service};

fn main() {
    // Чтение входных данных
    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: Создайте Product, используя product_name и product_cost

    // TODO: Создайте Service, используя service_name, hourly_rate и hours

    // TODO: Создайте Vec<Box<dyn Priceable>> и добавьте в него оба элемента

    // TODO: Пройдитесь по вектору и вычислите общую цену

    // TODO: Выведите итог с одним знаком после запятой
    // Формат: Total: ${total:.1}
}
quiz iconПроверьте себя

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

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

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