Object Orientated Rust (The rust book chapter 17 blog)
我想实现有关进一步使用Rust编程语言创建博客的选项的最后一个要点:
Allow users to add text content only when a post is in the
Draft state. Hint: have the state object responsible for what might change about the content but not responsible for modifying thePost .
我想实现
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 | 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, string: &str) { self.state.as_ref().unwrap().add_text(&mut self, string) } pub fn content(&self) -> &str { self.state.as_ref().unwrap().content(&self) } } trait State { fn content<'a>(&self, post: &'a Post) -> &'a str { "" } fn add_text<'a>(&self, post: &'a mut Post, string: &str) { ""; } } impl State for Draft { fn add_text<'a>(&self, post: &'a mut Post, string: &str) { &mut post.content.push_str(string); } } |
我的问题围绕
1 | self.state.as_ref().unwrap().add_text(&mut self, string) |
我得到了一个不能借来的可变的尝试删除
问题是
会发生什么情况
1 2 3 4 5 | impl State for Draft { fn add_text<'a>(&self, post: &'a mut Post, string: &str) { post.state = None; //killed self! } } |
此功能运行时,会使
Rust中不允许使用此使用模式,但是您有几种解决方法。最好的IMO是将要突变的数据分离为另一种类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | pub struct PostData { content: String, } pub struct Post { state: Option<Box<dyn State>>, data: PostData, } trait State { fn add_text<'a>(&self, post: &'a mut PostData, string: &str) { ""; } //... } impl Post { pub fn add_text(&mut self, string: &str) { self.state.as_ref().unwrap().add_text(&mut self.data, string) } } |
之所以有效,是因为您要借用
另一种简单的解决方法是从
1 2 3 4 5 6 7 8 | impl Post { pub fn add_text(&mut self, string: &str) { let state = self.state.take(); state.as_ref().unwrap().add_text(&mut self, string); assert!(self.state.is_none()); self.state = Some(state); } } |
我个人认为此解决方案有点怪异。
这里提供了一个解决方案
但是此解决方案有一些弃用警告。这是我的版本,用于修复警告
为了后人,这是提供的游乐场链接中的代码:
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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | 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 = self.state.as_ref().unwrap().add_text(&self.content, 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()) } } } 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 { "" } fn add_text(&self, original_text: &str, _text_to_add: &str) -> String { original_text.to_string() } } 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 add_text(&self, original_text: &str, text_to_add: &str) -> String { format!("{}{}", original_text, text_to_add) } } 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 {}) } } 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 } } fn main() { let mut post = Post::new(); post.add_text("I ate a salad for lunch today"); post.add_text("\ And a steak!"); assert_eq!("", post.content()); post.request_review(); post.add_text("\ And dessert!"); assert_eq!("", post.content()); post.approve(); post.add_text("\ And coffee!"); assert_eq!("I ate a salad for lunch today\ And a steak!", post.content()); } |
这是我的主意。
由于有人暗示国家将对可能发生的变化负责,而不是对Post进行修改,因此请在State特质中添加add_text(或更合适的函数名)。使其接受字符串切片。
1 2 3 4 5 6 | trait State { // Some codes here fn add_text<'a>(&self, _text: &'a str) -> &'a str { "" } } |
为Draft结构实现add_text方法:
1 2 3 4 5 6 | impl State for Draft { // Some codes here fn add_text<'a>(&self, text: &'a str) -> &'a str { text } } |
以这样的方式重新编写Post结构的add_text:它将调用状态的add_text并返回应添加到当前内容的"更改"。
1 2 3 4 5 6 7 8 9 10 | impl Post { // Some codes here pub fn add_text(&mut self, text: &str) { if let Some(s) = self.state.as_ref() { let text = s.add_text(text); self.content.push_str(text) } } // Some codes here } |
仅草稿状态将返回我们传递给它的字符串切片。我们将使用状态的add_text来确定要追加到当前内容的内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | fn main() { let mut post = Post::new(); post.add_text("I ate a salad for lunch today"); post.add_text("\ Then slept"); assert_eq!("", post.content()); post.request_review(); post.add_text("\ Then woke up"); assert_eq!("", post.content()); post.approve(); post.add_text("\ Then got back to work"); assert_eq!("I ate a salad for lunch today\ Then slept", post.content()); } |