Menu
Coddy logo textTech

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

Часть раздела Object Oriented Programming путешествия по 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 автоматически обрабатывает динамическую диспетчеризацию, вызывая правильную реализацию для каждого конкретного типа.

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

challenge icon

Задание

Легко

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

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

  • items.rs: Определите публичный трейт 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: $1450.0

И с входными данными Book, 29.99, Tutoring, 50.0 и 2.0:

Total: $130.0

Вы получите пять входных значений: название продукта, стоимость продукта, название услуги, почасовую ставку и количество отработанных часов (преобразуйте числовые значения в 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Проверьте себя

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

Все уроки раздела Object Oriented Programming