当前位置:   article > 正文

RUST博客帖子编辑示例

RUST博客帖子编辑示例

状态模式(state pattern)是一种面向对象的设计,它的关键点在于:一个值拥有的内部状态由数个状态对象(state object)表的而成,而值的行为则随着内部状态的改变而改变。

下面的示例用来实现发布博客的工作流程

  1. 在新建博客时生成一个空白的草稿文档,状态是Draft
  2. 在草稿编辑完毕后,请求对这个文章的状态进行审批(request_review),文档此时状态切换成了PendingReview
  3. 文章通过审批后对外正式发布,状态为Published
  4. 仅返回并打印成功发布后的文章,其他状态的文章都应该是对外不可见的

State trait定义了文章状态共享的行为,状态DraftPendReviewPublished都会实现State trait

State traitrequest_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
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

request_review

为了消耗旧的状态,request_review方法需要获取状态值的所有权。这也正是Poststate字段引入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.

content

content方法体中调用了Optionas_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> to Option<&T>,但例子中属于Option<T>Option<&T>的转换

代码冗余

示例的代码存在一个缺点:DraftPendReviewPublished重复实现了一些代码逻辑。你也许会试着提供默认实现,让State traitrequest_reviewapprove方法默认返回self。但这样的代码违背了对象安全规则,因为trait无法确定self的具体类型究竟是什么。如果我们希望将State作为trait对象来使用,那么它的方法就必须全部是对象安全的。

use

代码中使用了main.rslib.rs两个文件,在lib.rs也没有做任何mod的声明。在main.rs中通过使用blog::Post路径进行导,而不是crate::Post

根路径blog和我们配置文件Cargo.tomlpackage.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());
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/56269
推荐阅读
  

闽ICP备14008679号