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