網(wǎng)站開發(fā)流程主要分成什么seo外鏈代發(fā)
喜歡的話別忘了點贊、收藏加關(guān)注哦,對接下來的教程有興趣的可以關(guān)注專欄。謝謝喵!(=・ω・=)
17.3.1. 狀態(tài)模式
狀態(tài)模式(state pattern) 是一種面向?qū)ο笤O(shè)計模式,指的是一個值擁有的內(nèi)部狀態(tài)由數(shù)個狀態(tài)對象(state object) 表達而成,而值的行為隨著內(nèi)部狀態(tài)的改變而改變。
使用狀態(tài)模式意味著:業(yè)務(wù)需求變化時,不需要修改持有狀態(tài)的值的代碼,或者是使用這個值的代碼;只需要更新狀態(tài)對象內(nèi)部的代碼,以改變其規(guī)則,或者是增加一些新的狀態(tài)對象。
看個例子:
博客文章一開始是一個空草稿。草稿完成后,要求對該帖子進行審查。當帖子獲得批準后,就會發(fā)布。只有已發(fā)布的博客帖子才會返回要打印的內(nèi)容,因此不會意外發(fā)布未經(jīng)批準的帖子。
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());
}
- 使用
Post::new
創(chuàng)建新的博客文章草稿。首先創(chuàng)建一個Post
類型的實例,命名為post
。它是可變的,因為處于草稿狀態(tài)的文章還可以修改 - 然后通過
Post
上的add_text
方法增加了"I ate a salad for lunch today"這句話 - 接下來使用
request_review
方法請求審批 - 最后使用
approve
方法獲得審批通過
PS:添加的assert_eq!
在代碼中用于演示目的。單元測試可能包含斷言草稿博客文章從content
方法返回一個空字符串,但我們不打算為此示例編寫測試。
lib.rs
:
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 {""}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>;
}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> { Box::new(Published {}) }
}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 }
}
-
Post
結(jié)構(gòu)體有兩個字段,一個字段是state
,用于存儲文章當下的狀態(tài),它一共有三種狀態(tài):草稿、等待審批和已發(fā)布。Box<dyn State>
代表只要是實現(xiàn)了State
trait的類型就可以存入
通過這個字段,Post
類型能在內(nèi)部管理狀態(tài)與狀態(tài)之間的變化,這個狀態(tài)的變化是通過用戶調(diào)用Post
上的方法實現(xiàn)的,而用戶只能通過調(diào)用這些方法來改變值(因為Post
下的字段未設(shè)為公開,所以用戶沒辦法直接修改字段的值)。 -
下文通過
impl
塊為Post
實現(xiàn)了一些方法:-
new
函數(shù)用于創(chuàng)建一個Post
類型的實例,其初始的content
值是一個空的字符串;初始的state
處于草稿狀態(tài),所以state
存儲的是Draft
結(jié)構(gòu)體(下文有講) -
add_text
會往content
字段使用pusth_str
方法來添加內(nèi)容 -
即使我們調(diào)用了
add_text
并向帖子添加了一些內(nèi)容,我們?nèi)匀幌M?code>content方法返回一個空字符串切片,因為帖子仍處于草稿狀態(tài)。 -
request_review
會提取出state
字段下的狀態(tài),取出來之后,State
就會暫時變?yōu)?code>None,因為所有權(quán)被移動出來了。這個時候調(diào)用state
上的request_review
方法來請求審批。
當state
是Draft
狀態(tài)時,就會調(diào)用Draft
結(jié)構(gòu)體上的request_review
方法(下文有講),把state
字段的值從Draft
變?yōu)榱?code>PendingReview,把狀態(tài)更新回state
上。
-
-
approve
表示審批通過,其寫法跟request_review
差不多,把狀態(tài)取出來,調(diào)用self
上的approve
方法來更新狀態(tài)。 -
State
trait目前定義了兩個方法,只有簽名,沒有具體實現(xiàn):request_review
表示請求審批approve
表示審批通過
PS:注意它的簽名的參數(shù)是Box<self>
,與self
和mut self
有區(qū)別,Box<self>
意味著它只能被包裹著當前類型的Box
實例,它會在調(diào)用過程中獲取Box(self)
的所有權(quán),并使舊的實效,從而修改狀態(tài)。
-
Draft
用于表示草稿狀態(tài),不需要實際的內(nèi)容,所以只要聲明一個沒有字段的結(jié)構(gòu)體即可 -
通過
impl
塊為Draft
實現(xiàn)了State
trait:request_review
表示請求審批,把值變?yōu)榱?code>PendingReview。approve
表示審批通過。由于approve
在此時沒用,只需要把本身傳回去即可,所以返回值是self
。
-
PendingReviewing
用于表示等待審批,不需要實際的內(nèi)容,所以只要聲明一個沒有字段的結(jié)構(gòu)體即可 -
通過
impl
塊為PendingReview
實現(xiàn)了State
trait:request_review
表示請求審批,此時狀態(tài)不會變,只需要把本身傳回去即可,所以返回值是self
。approve
表示審批通過,返回Published
結(jié)構(gòu)體。
-
Published
用于表示已發(fā)表,不需要實際的內(nèi)容,所以只要聲明一個沒有字段的結(jié)構(gòu)體即可 -
通過
impl
塊為Published
實現(xiàn)了State
trait。但是它都處于已發(fā)布的狀態(tài)了,所以request_review
和approve
都沒啥用,直接返回本身self
就行。
我們?yōu)槭裁床皇褂妹杜e類型的變體作為帖子狀態(tài)?這當然是一個可能的解決方案,但它的其缺點之一是使用枚舉是每個檢查枚舉值的地方都需要一個match
表達式或類似的表達式來處理每個可能的變體。
這樣寫會存在很多重復的代碼,有些代碼根本沒用;但是它的優(yōu)點也很明顯:無論狀態(tài)值是什么Post
上的request_review
方法都不需要改變,每個狀態(tài)都負責自己的運行規(guī)則。
這里還有content
方法還需要修改,我們想要在發(fā)布狀態(tài)下使它可見,而其他兩種情況下看不到。一樣可以使用面向?qū)ο蟮脑O(shè)計模式。以下是原來的代碼:
pub fn content(&self) -> &str {""
}
首先在State
trait下定義content
方法:
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 {""}
}
寫了個默認實現(xiàn),返回空字符串。注意這里要使用生命周期,因為接收的是Post
的引用,然后返回的可能是Post
中某一部分的引用,所以返回值的生命周期和Post
參數(shù)的生命周期是相關(guān)聯(lián)的。
對于Draft
和PendingReview
來說默認實現(xiàn)就可以滿足需求了。只需要在Published
中寫一個方法覆蓋默認實現(xiàn):
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}
}
最后修改Post
上的content
方法:
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()) } }
}
我們需要先看Option
里面值的引用,所以說調(diào)用了as_ref
方法得到Option<&T>
,為了解包必須寫一步錯誤處理,用unwrap
即可。最后就調(diào)用content
方法,根據(jù)所處的狀態(tài)不同,content
的具體實現(xiàn)也會有所不同。
17.3.2. 狀態(tài)模式的取舍權(quán)衡
狀態(tài)模式的優(yōu)點如上所見:無論狀態(tài)值是什么Post
上的request_review
方法都不需要改變,每個狀態(tài)都負責自己的運行規(guī)則。
但它的缺點也比較明顯:
- 需要重復實現(xiàn)一些邏輯代碼
- 某些狀態(tài)之間是相互耦合的,如果我們新增一個狀態(tài),這時候跟它相關(guān)聯(lián)的代碼就需要修改
17.3.3. 將狀態(tài)和行為編碼為類型
如果我們嚴格按照面向?qū)ο蟮哪J綄懏斎皇强尚械?#xff0c;但是發(fā)揮不出Rust的全部威力。
下面我們會結(jié)合Rust的特點來修改,具體來說就是把狀態(tài)和行為改為具體的類型。Rust類型檢查系統(tǒng)會通過編譯時錯誤來阻止用戶使用無效的狀態(tài)。
修改后的代碼如下:
lib.rs
:
pub struct Post {content: String,
}pub struct DraftPost {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,}}
}pub struct PendingReviewPost {content: String,
}impl PendingReviewPost {pub fn approve(self) -> Post {Post {content: self.content,}}
}
-
聲明了
Post
和DraftPost
兩個結(jié)構(gòu)體,這兩者都有一個存儲String
類型的content
字段 -
通過
impl
塊寫了Post
的new
方法和content
方法:new
方法會創(chuàng)建一個空的DraftPost
結(jié)構(gòu)體content
方法就會返回本身的content
字段的值
-
通過
impl
塊寫了DraftPost
的方法:add_text
方法用于給DraftPost
的content
添加文字request_review
方法用于請求審批,調(diào)用這個方法就會返回另一個狀態(tài)PendingReviewPost
,表示正在審批中。這個狀態(tài)是在下文定義的
-
聲明了
PendingReviewPost
結(jié)構(gòu)體,有一個存儲String
類型的content
字段。通過impl
在它上面寫了一個approve
方法用于通過審批
這里的Post
就指正式發(fā)布之后的Post
,DraftPost
就代表還處于草稿狀態(tài)的文章,PendingReviewPost
表示正在審批的文章。審批成功就會把content
的值返回到Post
的content
字段里以供使用。
這樣寫不會出現(xiàn)意外的情況,因為只有通過審批正式發(fā)布的狀態(tài)Post
才有content
方法來獲取文章。
此時的main.rs
寫法也需要小改:
use blog::Post;fn main() {let mut post = Post::new();post.add_text("I ate a salad for lunch today");let post = post.request_review();let post = post.approve();assert_eq!("I ate a salad for lunch today", post.content());
}
17.3.4. 總結(jié)
Rust不僅能夠?qū)崿F(xiàn)面向?qū)ο蟮脑O(shè)計模式,還可以支持更多的模式。例如將狀態(tài)和行為編碼為類型。
面對對象的經(jīng)典模式并不總是Rust編程實踐中的最佳選擇,因為Rust具有其他面向?qū)ο笳Z言所沒有的所有權(quán)特性。