12. 面向对象篇¶
对应章节:第17章(trait 对象)。
本篇掌握 Rust 的面向对象风格:trait 对象与状态模式。
项目 19:博客文章状态机¶
目标:用 trait 对象实现"状态模式",理解 self: Box<Self>。
创建项目¶
说明:这是库项目(--lib),核心状态机放
src/lib.rs,测试也在其中。如需命令行演示,可自行添加src/main.rs(use blog::Post;)。
功能要求¶
状态流转:草稿 → 审核中 → 已发布
let mut post = Post::new();
post.add_text("今天学了 Rust");
assert_eq!(post.content(), ""); // 草稿不显示
post.request_review();
assert_eq!(post.content(), ""); // 审核中不显示
post.approve();
assert_eq!(post.content(), "今天学了 Rust"); // 已发布才显示
知识点¶
| 知识点 | 说明 |
|---|---|
Box<dyn State> |
trait 对象作为状态字段 |
self: Box<Self> |
按值接收装箱自身,可消耗并返回新状态 |
Option::take() |
拿走旧状态换新状态 |
| 默认方法 | trait 方法默认实现 |
| 状态模式 | 每个状态封装自己的转换规则 |
代码¶
src/lib.rs:
pub struct Post {
// state 用 Box<dyn State> 存"当前状态"(trait 对象,运行期才知道具体状态)
// Option 包装:转换状态时 take() 拿走旧状态,允许临时为空
state: Option<Box<dyn State>>,
content: String,
}
// 三个具体状态:空结构体,只承载"身份"
struct Draft;
struct PendingReview;
struct Published;
// 状态 trait:定义所有状态共有的行为
trait State {
// self: Box<Self>:按值接收"装箱的自身",消耗旧状态并返回新状态
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
// 默认实现:默认不显示内容,只有 Published 覆盖它
fn content<'a>(&self, _post: &'a Post) -> &'a str {
""
}
}
impl State for Draft {
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview) // 草稿 → 审核中
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self // 草稿不能直接通过,保持原样
}
}
impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self // 审核中不能再提交
}
fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published) // 审核通过 → 已发布
}
}
impl State for Published {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
// 已发布才返回真正的正文
fn content<'a>(&self, post: &'a Post) -> &'a str {
&post.content
}
}
impl Post {
pub fn new() -> Self {
Self {
state: Some(Box::new(Draft)), // 初始状态:草稿
content: String::new(),
}
}
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
// 委托给当前状态决定是否显示内容
pub fn content(&self) -> &str {
self.state.as_ref().unwrap().content(self)
}
pub fn request_review(&mut self) {
// take() 拿走旧状态 → 调用其方法得到新状态
if let Some(s) = self.state.take() {
self.state = Some(s.request_review());
}
}
pub fn approve(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.approve());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn draft_not_shown() {
let mut post = Post::new();
post.add_text("hello");
assert_eq!(post.content(), "");
}
#[test]
fn full_workflow() {
let mut post = Post::new();
post.add_text("hello");
post.request_review();
assert_eq!(post.content(), "");
post.approve();
assert_eq!(post.content(), "hello");
}
}
src/main.rs:
use blog::Post;
fn main() {
let mut post = Post::new();
post.add_text("今天学了 Rust的状态模式");
println!("草稿阶段,内容:{}", post.content());
post.request_review();
println!("审核中,内容:{}", post.content());
post.approve();
println!("已批准,内容:{}", post.content());
}
运行¶
注意事项(重点)¶
self: Box<Self>是状态转换的关键:方法按值吃掉Box里的自身,用完即弃并返回新状态——把"旧状态消失、新状态诞生"表达为天然语义。若用&self只能借用,无法消耗旧状态。Option的必要性:self.state.take()拿走旧 Box 瞬间字段为空,Option允许这种"临时为空"。content的默认方法:Draft/PendingReview 继承返回"",Published 覆盖返回&post.content;生命周期'a绑定 post 的借用。trait State私有也没关系:Post的公开方法通过内部调用 trait 方法,外部无需知道状态类型——封装了状态机。- 改进练习:目前没有覆盖"草稿直接 approve 无效"这条路径的测试——自己写一个:新建 Post 后直接调用
approve(),断言内容仍为空。