Menu
CoddyTech

Vector các 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 46 trên 61.

Bạn đã thấy Box<dyn Trait> cho phép một biến chứa các kiểu dữ liệu khác nhau. Sức mạnh thực sự thể hiện khi bạn kết hợp cách này với các bộ sưu tập: lưu trữ nhiều kiểu dữ liệu khác nhau trong cùng một vector.

Một vector thông thường yêu cầu tất cả phần tử phải cùng kiểu. Bạn không thể có một Vec<Dog> chứa cả mèo. Nhưng với các đối tượng trait, bạn có thể tạo một vector chứa bất kỳ thứ gì triển khai cùng một trait:

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

struct Dog;
struct Cat;
struct Bird;

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

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

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

fn main() {
    let animals: Vec<Box<dyn Speak>> = vec![
        Box::new(Dog),
        Box::new(Cat),
        Box::new(Bird),
    ];
}

Kiểu Vec<Box<dyn Speak>> có nghĩa là “một vector chứa các đối tượng trait được đóng hộp”. Mỗi phần tử là một Box trỏ đến một đối tượng triển khai Speak: các kiểu thực tế có thể khác nhau. Bạn bọc mỗi giá trị bằng Box::new() khi thêm nó vào vector.

Mẫu này rất cần thiết để xây dựng các hệ thống linh hoạt, nơi bạn cần quản lý tập hợp các đối tượng có liên quan nhưng khác nhau, như thành phần giao diện người dùng, thực thể trong trò chơi hoặc hệ thống plugin.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống thông báo có thể xử lý nhiều loại cảnh báo! Bạn sẽ tạo một tập hợp lưu trữ nhiều loại thông báo, gồm email, tin nhắn SMS và thông báo đẩy, tất cả trong cùng một vector bằng cách dùng các đối tượng trait.

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

  • notifications.rs: Định nghĩa một trait Notify công khai với phương thức send, nhận &self và trả về một String mô tả thông báo đang được gửi. Sau đó, tạo ba struct công khai:
    • Email: có trường recipient công khai (String). Phương thức send trả về Email to: {recipient}
    • Sms: có trường phone công khai (String). Phương thức send trả về SMS to: {phone}
    • Push: có trường device công khai (String). Phương thức send trả về Push to: {device}
  • main.rs: Đưa module notifications vào và tạo một vector có kiểu Vec<Box<dyn Notify>> chứa cả ba loại thông báo. Dùng các đầu vào được cung cấp để tạo mỗi loại thông báo một lần, thêm chúng vào vector theo thứ tự (Email, Sms, Push), rồi in kết quả gọi send() trên phần tử đầu tiên của vector.

Điểm cốt yếu ở đây là dù Email, Sms và Push là các struct hoàn toàn khác nhau, chúng vẫn có thể nằm cùng một vector vì cùng triển khai trait Notify. Mỗi phần tử được bọc bằng Box::new() để tạo đối tượng trait.

Đầu ra của bạn sẽ hiển thị thông báo đầu tiên:

Email to: {recipient}

Ví dụ, với các đầu vào alice@example.com, 555-1234 và iPhone-12:

Email to: alice@example.com

Bạn sẽ nhận ba đầu vào: địa chỉ email người nhận, số điện thoại và tên thiết bị.

Tự làm thử

mod notifications;

use notifications::{Notify, Email, Sms, Push};

fn main() {
    // Đọc các đầu vào
    let mut recipient = String::new();
    std::io::stdin().read_line(&mut recipient).expect("Failed to read line");
    let recipient = recipient.trim().to_string();

    let mut phone = String::new();
    std::io::stdin().read_line(&mut phone).expect("Failed to read line");
    let phone = phone.trim().to_string();

    let mut device = String::new();
    std::io::stdin().read_line(&mut device).expect("Failed to read line");
    let device = device.trim().to_string();

    // TODO: Tạo một Vec<Box<dyn Notify>> để chứa các kiểu thông báo khác nhau
    // TODO: Tạo các instance Email, Sms và Push từ các đầu vào
    // TODO: Thêm chúng vào vector theo thứ tự (Email, Sms, Push) bằng Box::new()
    // TODO: In ra kết quả khi gọi send() trên phần tử đầu tiên
}
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