写点什么

5 分钟速读之 Rust 权威指南(三十五)状态模式

用户头像
码生笔谈
关注
发布于: 3 小时前
5分钟速读之Rust权威指南(三十五)状态模式

实现状态模式

本节以面向对象设计模式中的状态模式为例,用 rust 来实现下面功能:


  1. 生成一份空白的草稿文档。

  2. 草稿完成后,请求对这篇草稿状态的文章进行审批。

  3. 在文章通过审批后正式发布。

  4. 仅返回成功发布后的文章,而不能发布没有通过审批的文章。

预期的效果

我们先看一下最终的使用方式:


// post/src/main.rsuse post::Post;let mut post = Post::new();post.add_text("中午我吃了沙拉");
println!("写稿中:{}", post.content()); // 写稿中:""
post.request_review();println!("审批中:{}", post.content()); // 审批中:""
post.approve();println!("已发布:{}", post.content()); // 已发布:"中午我吃了沙拉"
复制代码


首先定义文章结构体 Post:


// post/src/lib.rspub struct Post {  // 文章对应的状态对象,草稿、审批中、已发布分别对应不同的状态对象  // 至于为什么用Option类型,后面会解释  state: Option<Box<dyn State>>,  // 文章的内容  content: String,}
复制代码


定义 State trait,相当于定义了一个状态的接口,要求每一种文章状态都实现状态接口中的方法:


trait State {  // 请求审批方法,注意签名中需要获取self所有权,让座返回一个新状态  // 由于当使用方法时,调用者是Box智能指针,例如:post.state.request_review()  // 所以request_review参数中需要对self进行签名标注Box<Self>  fn request_review(self: Box<Self>) -> Box<dyn State>;
// 发布方法,同样需要获取获取权,并返回新状态 fn approve(self: Box<Self>) -> Box<dyn State>;
// 获取文章的内容,不需要获取所有权,传入post对象,将其content返回 fn content<'a>(&self, post: &'a Post) -> &'a str;}
复制代码


定义草案状态:


// 草案结构体struct Draft {}
// 为Draft实现State状态impl State for Draft { // 实现请求审批方法,这里self获取了原状态的所有权,意味着原状态不可用了 fn request_review(self: Box<Self>) -> Box<dyn State> { // 草案状态转换为审批中状态,这里返回审批中状态 Box::new(PendingReview {}) }
// 实现发布方法,同样获取原状态 fn approve(self: Box<Self>) -> Box<dyn State> { // 由于草案状态是不能直接发布的,这里返回self意味着调用无效的意思 self }
// 实现获取内容方法 fn content<'a>(&self, _post: &'a Post) -> &'a str { // 草案状态下是不能获取内容的,所以这里返回空字符串 "" }}
复制代码


定义审批中状态:


// 审批中结构体struct PendingReview {}
// 为PendingReview实现State状态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 {}) }
// 获取内容 fn content<'a>(&self, _post: &'a Post) -> &'a str { // 审批中也不能获取内容,返回控制符串 "" }}
复制代码


定义发布状态:


// 已发布结构体struct Published {}
// 为Published实现State状态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.as_str() }}
复制代码


实现 Post 转换状态的方法:


impl Post {  // 创建新的文章结构体  pub fn new() -> Self {    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 { // 将状态从Option::Some中取出,由于不需要取得所有权,所以这里取出的是引用 if let Some(state) = &self.state { // 将当前文章引用传递给状态对象 return state.content(&self) } else { "" } // 更简单的做法: // as_ref方法可以取出Some内容的引用,并返回Result类型, // 接着使用unwrap取到state对象的引用 self.state.as_ref().unwrap().content(&self) }
// 请求审批,需要改变状态,所以这里是可变引用 pub fn request_review(&mut self) { // 由于Rust不允许存在空值,如果使用if let Some(state) = self.state // 将会取到self.state的所有权,导致self.state为空了, // 但是在state.request_review方法签名中我们是需要state的所有权的。 // 所以,这里使用Option<T>的take方法来取出state字段的Some值, // 并在原来的位置留下一个None。这样做使我们能够将state的值从Post中移出来, // 最终取到state的所有权。 if let Some(state) = self.state.take() { // 转换为审批中状态 self.state = Some(state.request_review()) } }
// 发布文章,同样需要改变状态 pub fn approve(&mut self) { // 与请求发布状态转换一样,需要获取state的所有权 if let Some(state) = self.state.take() { // 转换为发布状态 self.state = Some(state.approve()) } }}
复制代码


至此便利用状态模式实现的文章的发布流程。

优化实现

上面使用面向对象的状态模式,未来再添加一种状态时,只需要再实现一种状态即可,非常方便,但是代码中却有很多重复的地方,例如每种状态中的 request_review 和 approve 方法,还有 Post 结构体中的 request_review 和 approve 方法。




严格按照面向对象语言的定义来实现一套状态模式自然是可行的,但这并不能发挥出 Rust 的全部威力。下面会修改部分代码来使 post 库可以将无效的状态和状态转移暴露为编译时错误。




将状态转移实现为不同类型之间的转换:


// post/src/lib.rs// 定义文章结构体pub struct Post {  content: String}impl Post {  // 创建一篇文章的初始状态是草案类型  pub fn new() -> DraftPost {    DraftPost {      content: String::new()    }  }  // 获取文章的内容  pub fn content(&self) -> &str {    self.content.as_str()  }}
// 定义草案结构体pub struct DraftPost { content: String}impl DraftPost { // 只有草案支持添加文字 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 } }}
复制代码


再使用一下:


// post/src/main.rsuse post::Post;
// 获取草案let mut draft = Post::new();draft.add_text("hello,");draft.add_text("world");
// 草案审批let pending_review_post = draft.request_review();// 草案审批通过,获得正式文章let post = pending_review_post.approve();println!("{}", post.content()); // hello,world
复制代码


经过上面的优化,剔除一些本不该属于某个状态下的一些方法,例如草案的发布方法、获取内容方法等,用户将不能再调用draft.content()draft.approve()等一些无意义的方法,整体的代码更加精简。


封面图:跟着Tina画美国


关注「码生笔谈」公众号,阅读更多最新章节

发布于: 3 小时前阅读数: 2
用户头像

码生笔谈

关注

欢迎关注「码生笔谈」公众号 2018.09.09 加入

前端、Rust干货分享,一起成为更好的Javascripter & Rustacean

评论

发布
暂无评论
5分钟速读之Rust权威指南(三十五)状态模式