赞
踩
状态模式(state pattern)是一种面向对象的设计,它的关键点在于:一个值拥有的内部状态由数个状态对象(state object)表的而成,而值的行为则随着内部状态的改变而改变。
下面的示例用来实现发布博客的工作流程:
Draft。request_review),文档此时状态切换成了PendingReview。Published。State trait定义了文章状态共享的行为,状态Draft、PendReview、Published都会实现State trait。
State trait中request_review声明中,第一个参数的类型是self: Box<Self>,而不是self、&self或&mut self。这个语法意味着该方法只能被包裹着当前实例的Box调用,它会在调用过程中获取Box<Self>的所有权并使旧的状态失效,从而将Post状态转换为一个新的状态。
// lib.rs 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; } 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()) } } } struct Draft {} impl State for Draft { fn request_review(self: Box<Self>) -> Box<dyn State> { Box::new(PendingReview {}) } fn approve(self: Box<Self>) -> Box<dyn State> { self } fn content<'a>(&self, post: &'a Post) -> &'a str { "" } } struct PendingReview {} 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 {} 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 } }
request_review为了消耗旧的状态,request_review方法需要获取状态值的所有权。这也正是Post的state字段引入Option的原因:RUST不允许结构体中出现未被填充的值。我们可以通过Option<T>的take方法来取出state字段的Some值,并在原来的位置留下一个None。
我们需要临时把state设置为None来取得state值的所有权,而不能直接使用self.state = self.state.request_review()这种代码,这可以确保Post无法在我们完成状态转换后再次使用旧的state值。
take方法的作用:Takes the value out of the option, leaving a [None] in its place.
contentcontent方法体中调用了Option的as_ref方法,因为我们需要的只是Option中值的引用,而不是它的所有权。由于state的类型是Option<Box<dyn State>>,所以我们在调用as_ref时得到Option<&Box<dyn State>>。如果这段代码中没有调用as_ref,就会导致编译错误,因为我们不能将state从函数参数的借用&self中移出。
我们需要在这个方法上添加相关的声明周期标注,该方法接受post的引用作为参数,并返回post中的content作为结果,因此,该方法中返回值的声明周期应该与post参数的声明周期相关。
as_ref方法声明:Converts from&Option<T>toOption<&T>,但例子中属于Option<T>到Option<&T>的转换
示例的代码存在一个缺点:Draft、PendReview、Published重复实现了一些代码逻辑。你也许会试着提供默认实现,让State trait的request_review和approve方法默认返回self。但这样的代码违背了对象安全规则,因为trait无法确定self的具体类型究竟是什么。如果我们希望将State作为trait对象来使用,那么它的方法就必须全部是对象安全的。
use代码中使用了main.rs和lib.rs两个文件,在lib.rs也没有做任何mod的声明。在main.rs中通过使用blog::Post路径进行导,而不是crate::Post。
根路径blog和我们配置文件Cargo.toml中package.name的声明有关系,根路径直接使用了包的名字。
// main.rs
use blog::Post;
fn main() {
let mut post = Post::new();
post.add_text("l go out to play");
assert_eq!("", post.content());
post.request_review();
assert_eq!("", post.content());
post.approve();
assert_eq!("l go out to play", post.content());
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。