Trait 向量
本课属于 Coddy Rust 学习旅程的“面向对象编程”单元。第 46 课,共 61 课。
你已经看到,Box<dyn Trait> 可以让单个变量保存不同的类型。当你把它与集合结合起来时,它的真正威力就显现出来了:在一个向量中存储多种不同的类型。
普通向量要求所有元素的类型都相同。你不能创建一个同时包含猫的 Vec<Dog>。不过,借助 trait 对象,你可以创建一个向量,其中可以存放任何实现了共同 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),
];
}
类型 Vec<Box<dyn Speak>> 表示“装有装箱特征对象的向量”。每个元素都是一个 Box,指向实现了 Speak 的对象:实际类型可以不同。将值添加到向量时,你需要用 Box::new() 包装每个值。
这种模式对于构建灵活的系统至关重要,尤其是在需要管理相关但不同的对象集合时,例如 UI 组件、游戏实体或插件系统。
挑战
简单让我们构建一个通知系统,用来处理不同类型的提醒!你将创建一个集合,用单个向量存储各种通知类型,包括电子邮件、SMS 消息和推送通知,并使用 trait 对象来实现。
你将把代码分成两个文件:
notifications.rs:定义一个公开的Notifytrait,其中包含一个send方法。该方法接收&self,并返回一个描述所发送通知的String。然后创建三个公开的结构体:Email:包含一个公开的recipient字段(String)。它的send应返回Email to: {recipient}Sms:包含一个公开的phone字段(String)。它的send应返回SMS to: {phone}Push:包含一个公开的device字段(String)。它的send应返回Push to: {device}
main.rs:引入 notifications 模块,并创建一个类型为Vec<Box<dyn Notify>>的向量,用来存放这三种通知类型。使用提供的输入创建每种通知各一个,按顺序(Email、Sms、Push)将它们添加到向量中,然后打印对向量第一个元素调用send()的结果。
这里的关键概念是,尽管 Email、Sms 和 Push 是完全不同的结构体,但由于它们共享 Notify trait,因此可以一起存放在同一个向量中。每个元素都用 Box::new() 包装,以创建 trait 对象。
你的输出应显示第一条通知:
Email to: {recipient}例如,输入 alice@example.com、555-1234 和 iPhone-12 时:
Email to: alice@example.com你将收到三个输入:电子邮件收件人、电话号码和设备名称。
亲手试试
mod notifications;
use notifications::{Notify, Email, Sms, Push};
fn main() {
// 读取输入
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: 创建一个 Vec<Box<dyn Notify>>,用于存放不同类型的通知
// TODO: 使用输入创建 Email、Sms 和 Push 实例
// TODO: 使用 Box::new(),按顺序(Email、Sms、Push)将它们添加到向量中
// TODO: 输出对第一个元素调用 send() 的结果
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Rust 在线编译器