Rust 状态模式(State Pattern)学习笔记
本文总结了 Rust《The Book》中关于状态模式的实现方式,并结合示例代码加以说明。
一、从 State trait 开始:定义所有状态共有的接口
整个状态模式的核心在于一个 trait:
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
fn content<'a>(&self, post: &'a Post) -> &'a str {
""
}
}
关键点如下:
- 不同于使用 self, &self, &mut self,这里使用self: Box<Self>,意味着该方法只可在持有这个类型的Box上被调用,即 a.request_review() 这里的a必须是 Box, A为实现了State的结构体。
- 这种写法常见于 Rust 的“状态模式”实现:每个状态都是一个独立的类型,而状态转换就像 Pokémon 进化一样——旧状态被消耗掉生成一个新状态对象,这样可以保证状态之间不会互相混杂
二、三个具体状态类型
1. Draft
struct Draft {}
行为:
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview {})
}
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
含义:
- 草稿被“提交审核”后进入 PendingReview。
- 草稿无法跳过审核直接“approve”,所以 approve 什么都不做。
2. PendingReview
struct PendingReview {}
行为:
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published {})
}
含义:
- 再次 request_review 不改变状态。
- 审核通过后进入 Published 状态。
3. Published
struct Published {}
这里Published覆盖了 content():
fn content<'a>(&self, post: &'a Post) -> &'a str {
post.content.as_ref()
}
含义:
- Published 是唯一能看到真正内容的状态。
- 其他状态通过默认实现返回空字符串。
三、Post结构体:把状态作为内部字段
理解完状态类型后,才需要看 Post 的结构:
pub struct Post {
content: String,
state: Option<Box<dyn State>>,
}
impl Post {
// Post的所有方法并不知道不同状态下有着不同的行为。这些都是在State这个trait中完成的
pub fn new() -> Post {
Post {
content: String::new(),
state: Some(Box::new(Draft {})),
}
}
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) {
match self.state.take() {
Some(s) => {
self.state = Some(s.request_review());
},
None => {}
}
}
pub fn approve(&mut self) {
match self.state.take() {
Some(s) => {
self.state = Some(s.approve());
},
None => {}
}
}
}
Post 有两部分:
- content:存储文本内容
- state:存储当前状态对象,哪种状态取决于 Box 内部的动态类型
注意:Post 本身不包含状态逻辑。
所有状态行为由 state 字段内部的对象决定。
状态转换逻辑:
- take() 把 state 的所有权移出
- 调用状态对象的转换方法
- 把新状态放回 state
四、总结
本例的状态模式有以下特点:
- 每种状态是独立类型
让不同状态的行为清晰、可维护。 - self: Box<Self> 强制状态转换是 move 操作
旧状态对象必然被丢弃,不会出现“状态混用”的问题。 - Post 本身没有写 if/else 或 match
所有逻辑都由状态对象负责,这符合“状态模式”的核心思想。 - Published 覆盖 content() 实现访问控制
利用 trait 默认实现完成简单权限控制。