Итерация по трейт-объектам
Часть раздела Объектно-ориентированное программирование путешествия по 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.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: Подключи модуль с элементами и создай вектор типа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}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Методы и поведение
Введение в блоки реализацииПараметр SelfИзменяемые методыАссоциированные функцииНесколько блоков реализацииЦепочки вызовов методовИтоги: действия с Rectangle4Проект: Виртуальный питомец
Определение питомцаКормление питомца2Инкапсуляция и модули
Основы модулейКлючевое слово PublicПриватные поляГеттерыСеттерыИтоги — Безопасный сейф5Обобщения
Обобщённые структурыОбобщённые методыНесколько обобщённых типовОбобщённые функцииПовторение — Координатная точка8Трейты как ограничения
Синтаксис ограничений трейтовМножественные ограниченияКонструкция whereВозврат типов с трейтамиИтоги — Обобщённый принтер11Паттерны проектирования в Rust
Паттерн NewtypeКомпозицияТрейт DropFrom и IntoИтоги — Мок-объект Smart Pointer3Продвинутые перечисления
Перечисления с даннымиМетоды в перечисленияхСопоставление вариантов с даннымиСнова об Option EnumИтоги — перечисление Shape6Определение трейтов
Что такое трейт?Реализация трейтовРеализации по умолчаниюПереопределение реализаций по умолчаниюТрейты с параметрамиИтоги — Media Player9Трейт-объекты
Динамическая диспетчеризацияВекторы трейтовИтерация по трейт-объектамПовторение — Менеджер зоопаркаПотренируйтесь самостоятельно: Онлайн-компилятор Rust