Menu
CoddyTech

Duyệt qua các đối tượng trait

Thuộc phần Lập trình hướng đối tượng trong hành trình Rust của Coddy. Bài học 47 trên 61.

Giờ đây, khi đã có thể lưu trữ nhiều kiểu khác nhau trong Vec<Box<dyn Trait>>, bước tiếp theo là xử lý tập hợp đó. Duyệt qua các đối tượng trait cũng giống như duyệt qua bất kỳ vector nào. Bạn lặp qua từng phần tử và gọi các phương thức được định nghĩa trong 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());
    }
}

Khi lặp qua for shape in &shapes, mỗi shape là một tham chiếu đến Box<dyn Shape>. Bạn có thể gọi trực tiếp bất kỳ phương thức nào từ trait Shape: Rust tự động xử lý việc điều phối động và gọi phần triển khai phù hợp cho từng kiểu cụ thể.

Mẫu này rất hữu ích để tổng hợp kết quả. Bạn có thể tính tổng diện tích, lọc hình theo kích thước hoặc biến đổi tập hợp. Điểm mấu chốt là khi các phần tử đã nằm trong một tập hợp đối tượng trait, bạn chỉ tương tác với chúng thông qua giao diện của trait, bất kể kiểu nền tảng của chúng là gì.

challenge icon

Thử thách

Dễ

Hãy tạo một trình tính giá cho giỏ hàng có thể chứa nhiều loại mặt hàng! Bạn sẽ xây dựng một hệ thống có thể lưu trữ chung các sản phẩm và dịch vụ, mỗi loại có cách tính giá riêng, rồi xử lý chúng để tính tổng giá.

Bạn sẽ sắp xếp code trong hai tệp:

  • items.rs: Định nghĩa một trait công khai Priceable với phương thức price, nhận &self và trả về một giá trị f64. Sau đó tạo hai struct công khai:
    • Product: có các trường công khai name (String) và cost (f64). Phương thức price chỉ cần trả về cost.
    • Service: có các trường công khai name (String), hourly_rate (f64) và hours (f64). Phương thức price cần trả về hourly_rate * hours.
  • main.rs: Khai báo module items và tạo một vector kiểu Vec<Box<dyn Priceable>>. Dùng đầu vào để tạo một Product và một Service, thêm chúng vào vector, sau đó duyệt qua tập hợp để tính và in ra tổng giá của tất cả mặt hàng.

Trait object thể hiện sức mạnh ở đây. Code duyệt của bạn không cần biết nó đang xử lý Product hay Service. Code chỉ cần gọi price() trên từng mặt hàng, Rust sẽ tự động gọi đúng phần triển khai.

Đầu ra của bạn cần hiển thị tổng giá với hai chữ số thập phân:

Total: ${total}

Ví dụ, với đầu vào Laptop, 999.99, Consulting, 150.0 và 3.0:

Total: $1449.99

Với đầu vào Book, 29.99, Tutoring, 50.0 và 2.0:

Total: $129.99

Bạn sẽ nhận năm đầu vào: tên sản phẩm, giá sản phẩm, tên dịch vụ, giá theo giờ và số giờ làm việc (phân tích các giá trị số thành kiểu f64).

Tự làm thử

mod items;

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

fn main() {
    // Đọc dữ liệu đầu vào
    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: Tạo Product bằng product_name và product_cost

    // TODO: Tạo Service bằng service_name, hourly_rate và hours

    // TODO: Tạo Vec<Box<dyn Priceable>> và thêm cả hai mục vào đó

    // TODO: Duyệt qua vectơ và tính tổng giá

    // TODO: In ra tổng giá với một chữ số sau dấu thập phân
    // Định dạng: Total: ${total:.1}
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Rust online