ch17-03-oo-design-patterns.md
commit 851449061b74d8b15adca936350a3fca6160ff39
狀態(tài)模式(state pattern)是一個面向?qū)ο笤O(shè)計模式。該模式的關(guān)鍵在于一個值有某些內(nèi)部狀態(tài),體現(xiàn)為一系列的 狀態(tài)對象,同時值的行為隨著其內(nèi)部狀態(tài)而改變。狀態(tài)對象共享功能:當(dāng)然,在 Rust 中使用結(jié)構(gòu)體和 trait 而不是對象和繼承。每一個狀態(tài)對象負責(zé)其自身的行為,以及該狀態(tài)何時應(yīng)當(dāng)轉(zhuǎn)移至另一個狀態(tài)。持有一個狀態(tài)對象的值對于不同狀態(tài)的行為以及何時狀態(tài)轉(zhuǎn)移毫不知情。
使用狀態(tài)模式意味著當(dāng)程序的業(yè)務(wù)需求改變時,無需改變值持有狀態(tài)或者使用值的代碼。我們只需更新某個狀態(tài)對象中的代碼來改變其規(guī)則,或者是增加更多的狀態(tài)對象。讓我們看看一個有關(guān)狀態(tài)模式和如何在 Rust 中使用它的例子。
為了探索這個概念,我們將實現(xiàn)一個增量式的發(fā)布博文的工作流。這個博客的最終功能看起來像這樣:
任何其他對博文的修改嘗試都是沒有作用的。例如,如果嘗試在請求審核之前通過一個草案博文,博文應(yīng)該保持未發(fā)布的狀態(tài)。
示例 17-11 展示這個工作流的代碼形式:這是一個我們將要在一個叫做 blog
的庫 crate 中實現(xiàn)的 API 的示例。這段代碼還不能編譯,因為還未實現(xiàn) blog
。
文件名: src/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());
}
示例 17-11: 展示了 blog
crate 期望行為的代碼
我們希望允許用戶使用 Post::new
創(chuàng)建一個新的博文草案。也希望能在草案階段為博文編寫一些文本。如果在審批之前嘗試立刻獲取博文的內(nèi)容,不應(yīng)該獲取到任何文本因為博文仍然是草案。一個好的單元測試將是斷言草案博文的 content
方法返回空字符串,不過我們并不準(zhǔn)備為這個例子編寫單元測試。
接下來,我們希望能夠請求審核博文,而在等待審核的階段 content
應(yīng)該仍然返回空字符串。最后當(dāng)博文審核通過,它應(yīng)該被發(fā)表,這意味著當(dāng)調(diào)用 content
時博文的文本將被返回。
注意我們與 crate 交互的唯一的類型是 Post
。這個類型會使用狀態(tài)模式并會存放處于三種博文所可能的狀態(tài)之一的值 —— 草案,等待審核和發(fā)布。狀態(tài)上的改變由 Post
類型內(nèi)部進行管理。狀態(tài)依庫用戶對 Post
實例調(diào)用的方法而改變,但是不能直接管理狀態(tài)變化。這也意味著用戶不會在狀態(tài)上犯錯,比如在過審前發(fā)布博文。
讓我們開始實現(xiàn)這個庫吧!我們知道需要一個公有 Post
結(jié)構(gòu)體來存放一些文本,所以讓我們從結(jié)構(gòu)體的定義和一個創(chuàng)建 Post
實例的公有關(guān)聯(lián)函數(shù) new
開始,如示例 17-12 所示。還需定義一個私有 trait State
。Post
將在私有字段 state
中存放一個 Option<T>
類型的 trait 對象 Box<dyn State>
。稍后將會看到為何 Option<T>
是必須的。
文件名: src/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(),
}
}
}
trait State {}
struct Draft {}
impl State for Draft {}
示例 17-12: Post
結(jié)構(gòu)體的定義和新建 Post
實例的 new
函數(shù),State
trait 和結(jié)構(gòu)體 Draft
State
trait 定義了所有不同狀態(tài)的博文所共享的行為,同時 Draft
、PendingReview
和 Published
狀態(tài)都會實現(xiàn) State
狀態(tài)?,F(xiàn)在這個 trait 并沒有任何方法,同時開始將只定義 Draft
狀態(tài)因為這是我們希望博文的初始狀態(tài)。
當(dāng)創(chuàng)建新的 Post
時,我們將其 state
字段設(shè)置為一個存放了 Box
的 Some
值。這個 Box
指向一個 Draft
結(jié)構(gòu)體新實例。這確保了無論何時新建一個 Post
實例,它都會從草案開始。因為 Post
的 state
字段是私有的,也就無法創(chuàng)建任何其他狀態(tài)的 Post
了!。Post::new
函數(shù)中將 content
設(shè)置為新建的空 String
。
在示例 17-11 中,展示了我們希望能夠調(diào)用一個叫做 add_text
的方法并向其傳遞一個 &str
來將文本增加到博文的內(nèi)容中。選擇實現(xiàn)為一個方法而不是將 content
字段暴露為 pub
。這意味著之后可以實現(xiàn)一個方法來控制 content
字段如何被讀取。add_text
方法是非常直觀的,讓我們在示例 17-13 的 impl Post
塊中增加一個實現(xiàn):
文件名: src/lib.rs
impl Post {
// --snip--
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
}
示例 17-13: 實現(xiàn)方法 add_text
來向博文的 content
增加文本
add_text
獲取一個 self
的可變引用,因為需要改變調(diào)用 add_text
的 Post
實例。接著調(diào)用 content
中的 String
的 push_str
并傳遞 text
參數(shù)來保存到 content
中。這不是狀態(tài)模式的一部分,因為它的行為并不依賴博文所處的狀態(tài)。add_text
方法完全不與 state
狀態(tài)交互,不過這是我們希望支持的行為的一部分。
即使調(diào)用 add_text
并向博文增加一些內(nèi)容之后,我們?nèi)匀幌M?nbsp;content
方法返回一個空字符串 slice,因為博文仍然處于草案狀態(tài),如示例 17-11 的第 8 行所示?,F(xiàn)在讓我們使用能滿足要求的最簡單的方式來實現(xiàn) content
方法:總是返回一個空字符串 slice。當(dāng)實現(xiàn)了將博文狀態(tài)改為發(fā)布的能力之后將改變這一做法。但是目前博文只能是草案狀態(tài),這意味著其內(nèi)容應(yīng)該總是空的。示例 17-14 展示了這個占位符實現(xiàn):
文件名: src/lib.rs
impl Post {
// --snip--
pub fn content(&self) -> &str {
""
}
}
列表 17-14: 增加一個 Post
的 content
方法的占位實現(xiàn),它總是返回一個空字符串 slice
通過增加這個 content
方法,示例 17-11 中直到第 8 行的代碼能如期運行。
接下來需要增加請求審核博文的功能,這應(yīng)當(dāng)將其狀態(tài)由 Draft
改為 PendingReview
。示例 17-15 展示了這個代碼:
文件名: src/lib.rs
impl Post {
// --snip--
pub fn request_review(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.request_review())
}
}
}
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
}
struct Draft {}
impl State for Draft {
fn request_review(self: Box<Self>) -> Box<dyn State> {
Box::new(PendingReview {})
}
}
struct PendingReview {}
impl State for PendingReview {
fn request_review(self: Box<Self>) -> Box<dyn State> {
self
}
}
示例 17-15: 實現(xiàn) Post
和 State
trait 的 request_review
方法
這里為 Post
增加一個獲取 self
可變引用的公有方法 request_review
。接著在 Post
的當(dāng)前狀態(tài)下調(diào)用內(nèi)部的 request_review
方法,并且第二個 request_review
方法會消費當(dāng)前的狀態(tài)并返回一個新狀態(tài)。
這里給 State
trait 增加了 request_review
方法;所有實現(xiàn)了這個 trait 的類型現(xiàn)在都需要實現(xiàn) request_review
方法。注意不同于使用 self
、 &self
或者 &mut self
作為方法的第一個參數(shù),這里使用了 self: Box<Self>
。這個語法意味著該方法只可在持有這個類型的 Box
上被調(diào)用。這個語法獲取了 Box<Self>
的所有權(quán)使老狀態(tài)無效化,以便 Post
的狀態(tài)值可轉(zhuǎn)換為一個新狀態(tài)。
為了消費老狀態(tài),request_review
方法需要獲取狀態(tài)值的所有權(quán)。這就是 Post
的 state
字段中 Option
的來歷:調(diào)用 take
方法將 state
字段中的 Some
值取出并留下一個 None
,因為 Rust 不允許結(jié)構(gòu)體實例中存在值為空的字段。這使得我們將 state
的值移出 Post
而不是借用它。接著我們將博文的 state
值設(shè)置為這個操作的結(jié)果。
我們需要將 state
臨時設(shè)置為 None
來獲取 state
值,即老狀態(tài)的所有權(quán),而不是使用 self.state = self.state.request_review();
這樣的代碼直接更新狀態(tài)值。這確保了當(dāng) Post
被轉(zhuǎn)換為新狀態(tài)后不能再使用老 state
值。
Draft
的 request_review
方法需要返回一個新的,裝箱的 PendingReview
結(jié)構(gòu)體的實例,其用來代表博文處于等待審核狀態(tài)。結(jié)構(gòu)體 PendingReview
同樣也實現(xiàn)了 request_review
方法,不過它不進行任何狀態(tài)轉(zhuǎn)換。相反它返回自身,因為當(dāng)我們請求審核一個已經(jīng)處于 PendingReview
狀態(tài)的博文,它應(yīng)該繼續(xù)保持 PendingReview
狀態(tài)。
現(xiàn)在我們能看出狀態(tài)模式的優(yōu)勢了:無論 state
是何值,Post
的 request_review
方法都是一樣的。每個狀態(tài)只負責(zé)它自己的規(guī)則。
我們將繼續(xù)保持 Post
的 content
方法實現(xiàn)不變,返回一個空字符串 slice?,F(xiàn)在我們可以擁有 PendingReview
狀態(tài)和 Draft
狀態(tài)的 Post
了,不過我們希望在 PendingReview
狀態(tài)下 Post
也有相同的行為。現(xiàn)在示例 17-11 中直到 10 行的代碼是可以執(zhí)行的!
approve
方法將與 request_review
方法類似:它會將 state
設(shè)置為審核通過時應(yīng)處于的狀態(tài),如示例 17-16 所示。
文件名: src/lib.rs
impl Post {
// --snip--
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 {
// --snip--
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
}
struct PendingReview {}
impl State for PendingReview {
// --snip--
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
}
}
示例 17-16: 為 Post
和 State
trait 實現(xiàn) approve
方法
這里為 State
trait 增加了 approve
方法,并新增了一個實現(xiàn)了 State
的結(jié)構(gòu)體,Published
狀態(tài)。
類似于 PendingReview
中 request_review
的工作方式,如果對 Draft
調(diào)用 approve
方法,并沒有任何效果,因為它會返回 self
。當(dāng)對 PendingReview
調(diào)用 approve
時,它返回一個新的、裝箱的 Published
結(jié)構(gòu)體的實例。Published
結(jié)構(gòu)體實現(xiàn)了 State
trait,同時對于 request_review
和 approve
兩方法來說,它返回自身,因為在這兩種情況博文應(yīng)該保持 Published
狀態(tài)。
現(xiàn)在需要更新 Post
的 content
方法。我們希望 content
根據(jù) Post
的當(dāng)前狀態(tài)返回值,所以需要 Post
代理一個定義于 state
上的 content
方法,如實例 17-17 所示:
文件名: src/lib.rs
impl Post {
// --snip--
pub fn content(&self) -> &str {
self.state.as_ref().unwrap().content(self)
}
// --snip--
}
示例 17-17: 更新 Post
的 content
方法來委托調(diào)用 State
的content
方法
因為目標(biāo)是將所有像這樣的規(guī)則保持在實現(xiàn)了 State
的結(jié)構(gòu)體中,我們將調(diào)用 state
中的值的 content
方法并傳遞博文實例(也就是 self
)作為參數(shù)。接著返回 state
值的 content
方法的返回值。
這里調(diào)用 Option
的 as_ref
方法是因為需要 Option
中值的引用而不是獲取其所有權(quán)。因為 state
是一個 Option<Box<dyn State>>
,調(diào)用 as_ref
會返回一個 Option<&Box<dyn State>>
。如果不調(diào)用 as_ref
,將會得到一個錯誤,因為不能將 state
移動出借用的 &self
函數(shù)參數(shù)。
接著調(diào)用 unwrap
方法,這里我們知道它永遠也不會 panic,因為 Post
的所有方法都確保在他們返回時 state
會有一個 Some
值。這就是一個第十二章 “當(dāng)我們比編譯器知道更多的情況” 部分討論過的我們知道 None
是不可能的而編譯器卻不能理解的情況。
接著我們就有了一個 &Box<dyn State>
,當(dāng)調(diào)用其 content
時,Deref 強制轉(zhuǎn)換會作用于 &
和 Box
,這樣最終會調(diào)用實現(xiàn)了 State
trait 的類型的 content
方法。這意味著需要為 State
trait 定義增加 content
,這也是放置根據(jù)所處狀態(tài)返回什么內(nèi)容的邏輯的地方,如示例 17-18 所示:
文件名: src/lib.rs
trait State {
// --snip--
fn content<'a>(&self, post: &'a Post) -> &'a str {
""
}
}
// --snip--
struct Published {}
impl State for Published {
// --snip--
fn content<'a>(&self, post: &'a Post) -> &'a str {
&post.content
}
}
示例 17-18: 為 State
trait 增加 content
方法
這里增加了一個 content
方法的默認實現(xiàn)來返回一個空字符串 slice。這意味著無需為 Draft
和 PendingReview
結(jié)構(gòu)體實現(xiàn) content
了。Published
結(jié)構(gòu)體會覆蓋 content
方法并會返回 post.content
的值。
注意這個方法需要生命周期注解,如第十章所討論的。這里獲取 post
的引用作為參數(shù),并返回 post
一部分的引用,所以返回的引用的生命周期與 post
參數(shù)相關(guān)。
現(xiàn)在示例完成了 —— 現(xiàn)在示例 17-11 中所有的代碼都能工作!我們通過發(fā)布博文工作流的規(guī)則實現(xiàn)了狀態(tài)模式。圍繞這些規(guī)則的邏輯都存在于狀態(tài)對象中而不是分散在 Post
之中。
我們展示了 Rust 是能夠?qū)崿F(xiàn)面向?qū)ο蟮臓顟B(tài)模式的,以便能根據(jù)博文所處的狀態(tài)來封裝不同類型的行為。Post
的方法并不知道這些不同類型的行為。通過這種組織代碼的方式,要找到所有已發(fā)布博文的不同行為只需查看一處代碼:Published
的 State
trait 的實現(xiàn)。
如果要創(chuàng)建一個不使用狀態(tài)模式的替代實現(xiàn),則可能會在 Post
的方法中,或者甚至于在 main
代碼中用到 match
語句,來檢查博文狀態(tài)并在這里改變其行為。這意味著需要查看很多位置來理解處于發(fā)布狀態(tài)的博文的所有邏輯!這在增加更多狀態(tài)時會變得更糟:每一個 match
語句都會需要另一個分支。
對于狀態(tài)模式來說,Post
的方法和使用 Post
的位置無需 match
語句,同時增加新狀態(tài)只涉及到增加一個新 struct
和為其實現(xiàn) trait 的方法。
這個實現(xiàn)易于擴展增加更多功能。為了體會使用此模式維護代碼的簡潔性,請嘗試如下一些建議:
reject
? 方法將博文的狀態(tài)從 ?PendingReview
? 變回 ?Draft
?Published
? 之前需要兩次 ?approve
? 調(diào)用Draft
? 狀態(tài)時增加文本內(nèi)容。提示:讓狀態(tài)對象負責(zé)內(nèi)容可能發(fā)生什么改變,但不負責(zé)修改 ?Post
?。狀態(tài)模式的一個缺點是因為狀態(tài)實現(xiàn)了狀態(tài)之間的轉(zhuǎn)換,一些狀態(tài)會相互聯(lián)系。如果在 PendingReview
和 Published
之間增加另一個狀態(tài),比如 Scheduled
,則不得不修改 PendingReview
中的代碼來轉(zhuǎn)移到 Scheduled
。如果 PendingReview
無需因為新增的狀態(tài)而改變就更好了,不過這意味著切換到另一種設(shè)計模式。
另一個缺點是我們會發(fā)現(xiàn)一些重復(fù)的邏輯。為了消除他們,可以嘗試為 State
trait 中返回 self
的 request_review
和 approve
方法增加默認實現(xiàn),不過這會違反對象安全性,因為 trait 不知道 self
具體是什么。我們希望能夠?qū)?nbsp;State
作為一個 trait 對象,所以需要其方法是對象安全的。
另一個重復(fù)是 Post
中 request_review
和 approve
這兩個類似的實現(xiàn)。他們都委托調(diào)用了 state
字段中 Option
值的同一方法,并在結(jié)果中為 state
字段設(shè)置了新值。如果 Post
中的很多方法都遵循這個模式,我們可能會考慮定義一個宏來消除重復(fù)(查看第十九章的 “宏” 部分)。
完全按照面向?qū)ο笳Z言的定義實現(xiàn)這個模式并沒有盡可能地利用 Rust 的優(yōu)勢。讓我們看看一些代碼中可以做出的修改,來將無效的狀態(tài)和狀態(tài)轉(zhuǎn)移變?yōu)榫幾g時錯誤。
我們將展示如何稍微反思狀態(tài)模式來進行一系列不同的權(quán)衡取舍。不同于完全封裝狀態(tài)和狀態(tài)轉(zhuǎn)移使得外部代碼對其毫不知情,我們將狀態(tài)編碼進不同的類型。如此,Rust 的類型檢查就會將任何在只能使用發(fā)布博文的地方使用草案博文的嘗試變?yōu)榫幾g時錯誤。
讓我們考慮一下示例 17-11 中 main
的第一部分:
文件名: src/main.rs
fn main() {
let mut post = Post::new();
post.add_text("I ate a salad for lunch today");
assert_eq!("", post.content());
}
我們?nèi)匀幌M軌蚴褂?nbsp;Post::new
創(chuàng)建一個新的草案博文,并能夠增加博文的內(nèi)容。不過不同于存在一個草案博文時返回空字符串的 content
方法,我們將使草案博文完全沒有 content
方法。這樣如果嘗試獲取草案博文的內(nèi)容,將會得到一個方法不存在的編譯錯誤。這使得我們不可能在生產(chǎn)環(huán)境意外顯示出草案博文的內(nèi)容,因為這樣的代碼甚至就不能編譯。示例 17-19 展示了 Post
結(jié)構(gòu)體、DraftPost
結(jié)構(gòu)體以及各自的方法的定義:
文件名: src/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);
}
}
示例 17-19: 帶有 content
方法的 Post
和沒有 content
方法的 DraftPost
Post
和 DraftPost
結(jié)構(gòu)體都有一個私有的 content
字段來儲存博文的文本。這些結(jié)構(gòu)體不再有 state
字段因為我們將狀態(tài)編碼改為結(jié)構(gòu)體類型。Post
將代表發(fā)布的博文,它有一個返回 content
的 content
方法。
仍然有一個 Post::new
函數(shù),不過不同于返回 Post
實例,它返回 DraftPost
的實例。現(xiàn)在不可能創(chuàng)建一個 Post
實例,因為 content
是私有的同時沒有任何函數(shù)返回 Post
。
DraftPost
上定義了一個 add_text
方法,這樣就可以像之前那樣向 content
增加文本,不過注意 DraftPost
并沒有定義 content
方法!如此現(xiàn)在程序確保了所有博文都從草案開始,同時草案博文沒有任何可供展示的內(nèi)容。任何繞過這些限制的嘗試都會產(chǎn)生編譯錯誤。
那么如何得到發(fā)布的博文呢?我們希望強制執(zhí)行的規(guī)則是草案博文在可以發(fā)布之前必須被審核通過。等待審核狀態(tài)的博文應(yīng)該仍然不會顯示任何內(nèi)容。讓我們通過增加另一個結(jié)構(gòu)體 PendingReviewPost
來實現(xiàn)這個限制,在 DraftPost
上定義 request_review
方法來返回 PendingReviewPost
,并在 PendingReviewPost
上定義 approve
方法來返回 Post
,如示例 17-20 所示:
文件名: src/lib.rs
impl DraftPost {
// --snip--
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,
}
}
}
列表 17-20: PendingReviewPost
通過調(diào)用 DraftPost
的 request_review
創(chuàng)建,approve
方法將 PendingReviewPost
變?yōu)榘l(fā)布的 Post
request_review
和 approve
方法獲取 self
的所有權(quán),因此會消費 DraftPost
和 PendingReviewPost
實例,并分別轉(zhuǎn)換為 PendingReviewPost
和發(fā)布的 Post
。這樣在調(diào)用 request_review
之后就不會遺留任何 DraftPost
實例,后者同理。PendingReviewPost
并沒有定義 content
方法,所以嘗試讀取其內(nèi)容會導(dǎo)致編譯錯誤,DraftPost
同理。因為唯一得到定義了 content
方法的 Post
實例的途徑是調(diào)用 PendingReviewPost
的 approve
方法,而得到 PendingReviewPost
的唯一辦法是調(diào)用 DraftPost
的 request_review
方法,現(xiàn)在我們就將發(fā)博文的工作流編碼進了類型系統(tǒng)。
這也意味著不得不對 main
做出一些小的修改。因為 request_review
和 approve
返回新實例而不是修改被調(diào)用的結(jié)構(gòu)體,所以我們需要增加更多的 let post =
覆蓋賦值來保存返回的實例。也不再能斷言草案和等待審核的博文的內(nèi)容為空字符串了,我們也不再需要他們:不能編譯嘗試使用這些狀態(tài)下博文內(nèi)容的代碼。更新后的 main
的代碼如示例 17-21 所示:
文件名: src/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-21: main
中使用新的博文工作流實現(xiàn)的修改
不得不修改 main
來重新賦值 post
使得這個實現(xiàn)不再完全遵守面向?qū)ο蟮臓顟B(tài)模式:狀態(tài)間的轉(zhuǎn)換不再完全封裝在 Post
實現(xiàn)中。然而,得益于類型系統(tǒng)和編譯時類型檢查,我們得到了的是無效狀態(tài)是不可能的!這確保了某些特定的 bug,比如顯示未發(fā)布博文的內(nèi)容,將在部署到生產(chǎn)環(huán)境之前被發(fā)現(xiàn)。
嘗試為示例 17-20 之后的 blog
crate 實現(xiàn)這一部分開始所建議的增加額外需求的任務(wù)來體會使用這個版本的代碼是何感覺。注意在這個設(shè)計中一些需求可能已經(jīng)完成了。
即便 Rust 能夠?qū)崿F(xiàn)面向?qū)ο笤O(shè)計模式,也有其他像將狀態(tài)編碼進類型這樣的模式存在。這些模式有著不同的權(quán)衡取舍。雖然你可能非常熟悉面向?qū)ο竽J剑匦滤伎歼@些問題來利用 Rust 提供的像在編譯時避免一些 bug 這樣有益功能。在 Rust 中面向?qū)ο竽J讲⒉豢偸亲詈玫慕鉀Q方案,因為 Rust 擁有像所有權(quán)這樣的面向?qū)ο笳Z言所沒有的功能。
閱讀本章后,不管你是否認為 Rust 是一個面向?qū)ο笳Z言,現(xiàn)在你都見識了 trait 對象是一個 Rust 中獲取部分面向?qū)ο蠊δ艿姆椒?。動態(tài)分發(fā)可以通過犧牲少量運行時性能來為你的代碼提供一些靈活性。這些靈活性可以用來實現(xiàn)有助于代碼可維護性的面向?qū)ο竽J?。Rust 也有像所有權(quán)這樣不同于面向?qū)ο笳Z言的功能。面向?qū)ο竽J讲⒉豢偸抢?Rust 優(yōu)勢的最好方式,但也是可用的選項。
接下來,讓我們看看另一個提供了多樣靈活性的 Rust 功能:模式。貫穿全書的模式, 我們已經(jīng)和它們打過照面了,但并沒有見識過它們的全部本領(lǐng)。讓我們開始探索吧!
更多建議: