Rust学习笔记(五十二)实现一种面向对象设计模式(状态模式)
状态模式
状态模式(state pattern)是一种面向对象设计模式:一个值拥有的内部状态是由数个状态对象(state object)表达而成,而值的行为则随着内部状态的改变而改变。
使用状态模式意味着: 当业务需求变化时,不需要修改持有状态的值的代码,或者使用该值的代码 只需要更新状态对象内部的代码,以便于改变其规则。或者增加一些新的状态对象
例如有一个发布博客的工作流程: 新建博文时生成一个空白的草稿文档 在草稿编写完后可以请求对草稿进行审批 审批通过后可以对外正式发布
并且要求只能打印返回的已经成功发布的文章,不能打印未成功发布的文章。 //src/main.rs use blog::Post; fn main() { let mut post = Post::new();//新建空白草稿 post.add_text("I ate a salad for lunch today");//编写博文 assert_eq!("", post.content());//未成功发布不能打印内容 post.request_review();//请求审批 assert_eq!("", post.content());//未成功发布不能打印内容 post.approve();//审批通过 assert_eq!("I ate a salad for lunch today", post.content());//审批通过,可以打印成功发布的文章 } //src/lib.rs pub struct Post { state: Option>,//持有文章当前状态 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 {//因为返回的内容由状态决定,所以调用对应状态的content方法 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()) } } } trait State { fn request_review(self: Box) -> Box;//请求审批 fn approve(self: Box) -> Box;//审批通过 fn content<"a>(&self, post: &"a Post) -> &"a str {//默认返回空字符串,因为接收的是Post的引用,返回的可能是Post中的某一部分,所以需要加生命周期 "" } } //由于草稿和待审批状态不能返回内容,所以默认实现就足够了 struct Draft {}//草稿状态 impl State for Draft { fn request_review(self: Box) -> Box {//返回待审批状态 Box::new(PendingReview {}) } fn approve(self: Box) -> Box {//没有意义 self } } struct PendingReview {}//等待审批状态 impl State for PendingReview { fn request_review(self: Box) -> Box {//因为本身就是待审批状态,所以返回自身 self } fn approve(self: Box) -> Box {//待审批通过,状态转为已发布 Box::new(Published {}) } } struct Published {}//已发布状态 impl State for Published { fn request_review(self: Box) -> Box {//没有意义 self } fn approve(self: Box) -> Box {//没有意义 self } fn content<"a>(&self, post: &"a Post) -> &"a str {//已发布状态可以返回内容,所以直接返回Post的内容 &post.content } } 状态模式的取舍权衡
缺点: 某些状态之间是耦合的,例如新增一个状态,就需要修改跟其相关联的状态 需要重复实现一些代码逻辑,比如request_review和approve等 将状态和行为编码为类型
把状态和行为改为具体的类型,这样做可以:Rust类型检查系统会通过编译时错误来阻止用户使用无效的状态。例: //src/lib.rs pub struct Post { //发布成功的类型 content: String, } pub struct DraftPost { //草稿类型 content: String, } pub struct PendingReviewPost { //待审批类型 content: String, } impl Post { pub fn new() -> DraftPost { DraftPost { content: String::new(), } } pub fn content(&self) -> &str { &self.content } } 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, } } } impl PendingReviewPost { pub fn approve(self) -> Post { Post { content: self.content, } } } //src/main.rs use blog::Post; fn main() { let mut post = Post::new(); post.add_text("I ate a salad for lunch today"); assert_eq!("", post.content());//因为DraftPost没有实现content方法,所以编译器报错 let post = post.request_review(); assert_eq!("", post.content());//因为PendingReviewPost没有实现content方法,所以编译器报错 let post = post.approve(); assert_eq!("I ate a salad for lunch today", post.content()); }
所以将状态和行为编码为类型,能在写代码/编译时发现在某种状态上不该发生的行为。 总结
Rust不仅能实现面向对象的设计模式,还可以支持更多的模式。但是面向对象的经典模式并不总是Rust编程实践的最佳选择,因为Rust具有所有权等其它面向对象语言没有的特性!所以在写Rust代码时,不要总考虑面向对象的一些概念。