1. 需求背景
我们希望实现一个简单的博客发布系统,遵循以下工作流:
- 博客文章(
Post
)从草稿(Draft) 状态开始。 - 当草稿完成后,可以请求审核(Pending Review)。
- 审核通过后,文章进入已发布(Published) 状态。
- 只有已发布的文章可以显示内容。
此外,系统还需要保证:
- 未经审核的文章不能直接发布。
- 文章在正确的状态下才允许执行特定操作。
2. 传统面向对象方式的实现
在传统面向对象语言(如 Java 或 C++)中,我们通常会使用基类 (State
)和子类(不同的状态)来实现状态模式。
在 Rust 中,我们可以使用 trait 对象(trait objects) 来模拟这一模式。
2.1 定义 State
trait 和 Post
结构体
我们首先定义一个 State
trait,表示所有状态的共有行为:
rust
pub 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 {
""
}
}
然后,我们定义 Post
结构体,它持有状态对象,并提供一些管理状态的方法:
rust
pub struct Post {
state: Option<Box<dyn State>>, // 状态对象
content: String, // 文章内容
}
impl Post {
pub fn new() -> Post {
Post {
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) {
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());
}
}
}
2.2 定义不同的状态结构体
rust
pub struct Draft;
impl State for Draft {
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview {})
}
}
pub struct PendingReview;
impl State for PendingReview {
fn approve(self: Box<Self>) -> Box<dyn State> {
Box::new(Published {})
}
}
pub struct Published;
impl State for Published {
fn content<'a>(&self, post: &'a Post) -> &'a str {
&post.content
}
}
2.3 测试博客发布系统
rust
fn main() {
let mut post = Post::new();
post.add_text("Hello, Rust!");
// 草稿状态下内容不可见
assert_eq!("", post.content());
// 请求审核
post.request_review();
assert_eq!("", post.content());
// 审核通过
post.approve();
assert_eq!("Hello, Rust!", post.content());
}
2.4 传统面向对象方式的特点
- 封装状态行为 :不同的状态对象各自实现
State
trait,封装了自己的行为。 - 动态分派(Dynamic Dispatch) :
Box<dyn State>
允许Post
处理任意实现State
的状态。 - 状态转换由状态对象决定 :状态对象自己决定何时转换为其他状态,而
Post
只是持有状态对象。
但这种方式仍然存在一些问题:
- 运行时开销:使用 trait 对象引入了动态分派,会影响性能。
- 无编译时状态约束:状态转换的逻辑仍然是运行时检查的,不能在编译期防止非法状态转换。
3. Rust 方式:利用类型系统优化状态模式
Rust 的类型系统非常强大,我们可以用 不同的结构体表示不同状态,避免动态分派,提高安全性。
3.1 定义 Post
及状态结构体
rust
pub struct Post {
content: String,
}
pub struct DraftPost {
content: String,
}
impl Post {
pub fn content(&self) -> &str {
&self.content
}
}
impl DraftPost {
pub fn new() -> DraftPost {
DraftPost {
content: String::new(),
}
}
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
pub fn request_review(self) -> PendingReviewPost {
PendingReviewPost {
content: self.content,
}
}
}
pub struct PendingReviewPost {
content: String,
}
impl PendingReviewPost {
pub fn approve(self) -> Post {
Post {
content: self.content,
}
}
}
3.2 新实现的特点
- 状态转换由类型系统管理 :不同状态的
struct
之间进行转换,编译期就能保证正确性。 - 去除了动态分派:不使用 trait 对象,所有方法调用都是静态分派,性能更优。
- 无非法状态 :无法创建
Post
直接进入Published
状态。
4. 结论
Rust 强大的类型系统使得我们可以用静态方式实现状态模式,而无需动态分派。两种实现方式各有优劣:
实现方式 | 运行时开销 | 类型安全 | 适用场景 |
---|---|---|---|
Trait 对象 | 动态分派 | 运行时检查 | 适用于状态较多、变化频繁的场景 |
类型系统 | 静态分派 | 编译时检查 | 适用于状态转换固定的场景 |
在 Rust 开发中,我们可以根据项目需求选择合适的模式,充分利用 Rust 类型系统的优势,提高代码安全性和运行效率!