Menu
CoddyTech

Điều phối động

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 45 trên 61.

Khi bạn dùng generic với các ràng buộc trait như <T: Summary>, Rust xác định kiểu chính xác tại thời điểm biên dịch. Cách này gọi là điều phối tĩnh (static dispatch): trình biên dịch tạo mã chuyên biệt cho từng kiểu cụ thể mà bạn dùng. Cách này nhanh, nhưng có một hạn chế: một biến chỉ có thể chứa một kiểu cụ thể.

Nếu bạn cần một biến có thể chứa các kiểu dữ liệu khác nhau trong khi chạy thì sao? Đây là lúc đối tượng trait phát huy tác dụng. Với Box<dyn Trait>, bạn có thể lưu bất kỳ kiểu dữ liệu nào triển khai trait:

trait Speak {
    fn speak(&self) -> String;
}

struct Dog;
struct Cat;

impl Speak for Dog {
    fn speak(&self) -> String { String::from("Woof!") }
}

impl Speak for Cat {
    fn speak(&self) -> String { String::from("Meow!") }
}

fn main() {
    let animal: Box<dyn Speak> = Box::new(Dog);
    println!("{}", animal.speak());  // "Woof!"
    
    let animal: Box<dyn Speak> = Box::new(Cat);
    println!("{}", animal.speak());  // "Meow!"
}

Từ khóa dyn cho biết cơ chế điều phối động (dynamic dispatch): Rust tìm phương thức cần gọi khi chạy chương trình thay vì lúc biên dịch. Box là cần thiết vì các đối tượng trait không có kích thước xác định; Box cung cấp một con trỏ có kích thước cố định.

Hãy nghĩ theo cách này: kiểu tổng quát (generics) nói rằng “mình làm việc với kiểu T”, còn đối tượng trait nói rằng “mình làm việc với bất cứ thứ gì có thể làm việc này”. Đổi lại, bạn phải chấp nhận một chi phí nhỏ khi chạy chương trình để linh hoạt xử lý các kiểu khác nhau thông qua cùng một biến.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống cho thuê phương tiện để thấy sức mạnh của cơ chế điều phối động! Bạn sẽ tạo một trait (đặc tính) định nghĩa những gì mọi phương tiện cho thuê có thể làm, rồi dùng Box<dyn Trait> để lưu các kiểu phương tiện khác nhau trong cùng một biến.

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

  • vehicle.rs: Định nghĩa trait Rentable công khai, có phương thức rental_info nhận &self và trả về một String. Sau đó, tạo hai struct công khai:
    • Car: có trường model công khai (String). Phương thức rental_info sẽ trả về Car: {model}
    • Bike: có trường brand công khai (String). Phương thức rental_info sẽ trả về Bike: {brand}
  • main.rs: Đưa module phương tiện vào và dùng các đầu vào để minh họa cơ chế điều phối động. Tạo một biến Box<dyn Rentable> ban đầu lưu một Car, in thông tin cho thuê của nó, sau đó gán lại cùng biến đó để lưu một Bike và in thông tin cho thuê của nó lần nữa.

Điều thú vị ở đây là một biến duy nhất có kiểu Box<dyn Rentable> có thể lưu một Car hoặc một Bike: Rust sẽ xác định cần gọi phương thức rental_info nào khi chương trình chạy. Đây chính là cơ chế điều phối động!

Đầu ra của bạn sẽ hiển thị thông tin của cả hai phương tiện:

Car: {model}
Bike: {brand}

Ví dụ, với đầu vào Tesla Model 3 và Trek:

Car: Tesla Model 3
Bike: Trek

Bạn sẽ nhận hai đầu vào: mẫu xe hơi và thương hiệu xe đạp.

Tự làm thử

mod vehicle;

use vehicle::{Rentable, Car, Bike};

fn main() {
    // Đọc đầu vào
    let mut car_model = String::new();
    std::io::stdin().read_line(&mut car_model).expect("Failed to read line");
    let car_model = car_model.trim().to_string();
    
    let mut bike_brand = String::new();
    std::io::stdin().read_line(&mut bike_brand).expect("Failed to read line");
    let bike_brand = bike_brand.trim().to_string();
    
    // TODO: Tạo biến kiểu Box<dyn Rentable> để chứa một Car
    // In ra thông tin cho thuê của Car bằng phương thức rental_info()
    
    // TODO: Gán lại cho biến đó một Bike
    // In ra thông tin cho thuê của Bike lần nữa
    
}
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