在 Rust 中实现面向对象的状态模式

1. 需求背景

我们希望实现一个简单的博客发布系统,遵循以下工作流:

  1. 博客文章(Post)从草稿(Draft) 状态开始。
  2. 当草稿完成后,可以请求审核(Pending Review)
  3. 审核通过后,文章进入已发布(Published) 状态。
  4. 只有已发布的文章可以显示内容。

此外,系统还需要保证:

  • 未经审核的文章不能直接发布。
  • 文章在正确的状态下才允许执行特定操作。

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 类型系统的优势,提高代码安全性和运行效率!

相关推荐
小羊没烦恼!11 小时前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
伞伞悦读12 小时前
【第38期】Python 模块与包详解:import、from、模块搜索路径、包结构和 __init__
开发语言·python
C语言小火车13 小时前
C/C++ 为什么需要编译器?
开发语言·c++
霍霍的袁14 小时前
【C++】map 和 set 的使用 | 从用法到底层
开发语言·c++·学习·visual studio
孙启超14 小时前
【AI开发之Rust】第 11 课:智能指针与内部可变性
开发语言·后端·rust
此生决int14 小时前
深入理解C++系列(20)——C++11(下)
开发语言·c++
mikuyyds15 小时前
geo-toolbox 水文插件算法解析:从 Muskingum 到 Muskingum-Cunge
算法·rust·gis
PC2005-cloud15 小时前
Rust学习笔记:模块系统——mod、use、pub与多文件项目
rust
CoderYanger16 小时前
A.每日一题:835. 图像重叠
java·开发语言·程序人生·leetcode·面试·职场和发展·学习方法
Amos_Web16 小时前
Rspack 源码解析(十二):JavaScript Chunk 是如何被渲染出来的
前端·rust·源码