トレイトオブジェクトの反復処理
CoddyのRustジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 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 を使って Iterate すると、各 shape は Box<dyn Shape> への参照になります。Shape トレイトの任意のメソッドを直接呼び出せます。Rust が動的ディスパッチを自動的に処理し、具体的な型ごとに正しい実装を呼び出します。
このパターンは結果を集約するうえで強力です。すべての面積を合計したり、サイズで図形をフィルタリングしたり、コレクションを変換したりできます。重要な点は、item がトレイトオブジェクトのコレクションに入ると、基盤となる型に関係なく、トレイトのインターフェースを通じてのみ操作するということです。
チャレンジ
簡単異なる種類の item を保持できるショッピングカート用の価格計算機を作りましょう!異なる価格計算ロジックを持つ Product と Service を一緒に格納し、処理して合計価格を計算できるシステムを作成します。
コードは2つのファイルに分けて整理します。
items.rs:pricemethod を持つ public なPriceabletrait を Define します。この method は&selfを受け取り、f64を returns します。次に、2つの public な struct を作成します。Product:public なname(String) フィールドとcost(f64) フィールドを持ちます。そのpriceは単純にcostを returns する必要があります。Service:public なname(String)、hourly_rate(f64)、hours(f64) フィールドを持ちます。そのpriceはhourly_rate * hoursを returns する必要があります。
main.rs:items module を取り込み、型Vec<Box<dyn Priceable>>の vector を作成します。inputs を使用して1つの Product と1つの Service を作成し、それらを vector に add してから、collection を Iterate してすべての item の合計価格を calculate し、Print します。
ここで trait objects の力が発揮されます。Iteration code は、Product を処理しているのか Service を処理しているのかを知る必要がありません。各 item に対して単に price() を呼び出すだけで、Rust が正しい実装へ自動的に dispatch します。
出力には合計価格を小数点以下2桁で表示する必要があります。
Total: ${total}たとえば、inputs が Laptop、999.99、Consulting、150.0、3.0 の場合:
Total: $1449.99また、inputs が Book、29.99、Tutoring、50.0、2.0 の場合:
Total: $129.995つの input を受け取ります:product name、product cost、service name、hourly rate、worked hours(数値は f64 として parse します)。
自分で試してみよう
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_name と product_cost を使用して Product を作成する
// TODO: service_name、hourly_rate、hours を使用して Service を作成する
// TODO: Vec<Box<dyn Priceable>> を作成し、両方のアイテムを追加する
// TODO: ベクトルを反復処理して合計価格を計算する
// TODO: 合計を小数点以下1桁で出力する
// 形式: Total: ${total:.1}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rustオンラインコンパイラ