国产gaysexchina男同gay,japanrcep老熟妇乱子伦视频,吃奶呻吟打开双腿做受动态图,成人色网站,国产av一区二区三区最新精品

Rust 面向?qū)ο笤O(shè)計(jì)模式的實(shí)現(xiàn)

2023-03-22 15:15 更新
ch17-03-oo-design-patterns.md
commit 851449061b74d8b15adca936350a3fca6160ff39

狀態(tài)模式state pattern)是一個(gè)面向?qū)ο笤O(shè)計(jì)模式。該模式的關(guān)鍵在于一個(gè)值有某些內(nèi)部狀態(tài),體現(xiàn)為一系列的 狀態(tài)對(duì)象,同時(shí)值的行為隨著其內(nèi)部狀態(tài)而改變。狀態(tài)對(duì)象共享功能:當(dāng)然,在 Rust 中使用結(jié)構(gòu)體和 trait 而不是對(duì)象和繼承。每一個(gè)狀態(tài)對(duì)象負(fù)責(zé)其自身的行為,以及該狀態(tài)何時(shí)應(yīng)當(dāng)轉(zhuǎn)移至另一個(gè)狀態(tài)。持有一個(gè)狀態(tài)對(duì)象的值對(duì)于不同狀態(tài)的行為以及何時(shí)狀態(tài)轉(zhuǎn)移毫不知情。

使用狀態(tài)模式意味著當(dāng)程序的業(yè)務(wù)需求改變時(shí),無(wú)需改變值持有狀態(tài)或者使用值的代碼。我們只需更新某個(gè)狀態(tài)對(duì)象中的代碼來(lái)改變其規(guī)則,或者是增加更多的狀態(tài)對(duì)象。讓我們看看一個(gè)有關(guān)狀態(tài)模式和如何在 Rust 中使用它的例子。

為了探索這個(gè)概念,我們將實(shí)現(xiàn)一個(gè)增量式的發(fā)布博文的工作流。這個(gè)博客的最終功能看起來(lái)像這樣:

  1. 博文從空白的草案開(kāi)始。
  2. 一旦草案完成,請(qǐng)求審核博文。
  3. 一旦博文過(guò)審,它將被發(fā)表。
  4. 只有被發(fā)表的博文的內(nèi)容會(huì)被打印,這樣就不會(huì)意外打印出沒(méi)有被審核的博文的文本。

任何其他對(duì)博文的修改嘗試都是沒(méi)有作用的。例如,如果嘗試在請(qǐng)求審核之前通過(guò)一個(gè)草案博文,博文應(yīng)該保持未發(fā)布的狀態(tài)。

示例 17-11 展示這個(gè)工作流的代碼形式:這是一個(gè)我們將要在一個(gè)叫做 blog 的庫(kù) crate 中實(shí)現(xiàn)的 API 的示例。這段代碼還不能編譯,因?yàn)檫€未實(shí)現(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 期望行為的代碼

我們希望允許用戶(hù)使用 Post::new 創(chuàng)建一個(gè)新的博文草案。也希望能在草案階段為博文編寫(xiě)一些文本。如果在審批之前嘗試立刻獲取博文的內(nèi)容,不應(yīng)該獲取到任何文本因?yàn)椴┪娜匀皇遣莅?。一個(gè)好的單元測(cè)試將是斷言草案博文的 content 方法返回空字符串,不過(guò)我們并不準(zhǔn)備為這個(gè)例子編寫(xiě)單元測(cè)試。

接下來(lái),我們希望能夠請(qǐng)求審核博文,而在等待審核的階段 content 應(yīng)該仍然返回空字符串。最后當(dāng)博文審核通過(guò),它應(yīng)該被發(fā)表,這意味著當(dāng)調(diào)用 content 時(shí)博文的文本將被返回。

注意我們與 crate 交互的唯一的類(lèi)型是 Post。這個(gè)類(lèi)型會(huì)使用狀態(tài)模式并會(huì)存放處于三種博文所可能的狀態(tài)之一的值 —— 草案,等待審核和發(fā)布。狀態(tài)上的改變由 Post 類(lèi)型內(nèi)部進(jìn)行管理。狀態(tài)依庫(kù)用戶(hù)對(duì) Post 實(shí)例調(diào)用的方法而改變,但是不能直接管理狀態(tài)變化。這也意味著用戶(hù)不會(huì)在狀態(tài)上犯錯(cuò),比如在過(guò)審前發(fā)布博文。

定義 Post 并新建一個(gè)草案狀態(tài)的實(shí)例

讓我們開(kāi)始實(shí)現(xiàn)這個(gè)庫(kù)吧!我們知道需要一個(gè)公有 Post 結(jié)構(gòu)體來(lái)存放一些文本,所以讓我們從結(jié)構(gòu)體的定義和一個(gè)創(chuàng)建 Post 實(shí)例的公有關(guān)聯(lián)函數(shù) new 開(kāi)始,如示例 17-12 所示。還需定義一個(gè)私有 trait State。Post 將在私有字段 state 中存放一個(gè) Option<T> 類(lèi)型的 trait 對(duì)象 Box<dyn State>。稍后將會(huì)看到為何 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 實(shí)例的 new 函數(shù),State trait 和結(jié)構(gòu)體 Draft

State trait 定義了所有不同狀態(tài)的博文所共享的行為,同時(shí) DraftPendingReview 和 Published 狀態(tài)都會(huì)實(shí)現(xiàn) State 狀態(tài)?,F(xiàn)在這個(gè) trait 并沒(méi)有任何方法,同時(shí)開(kāi)始將只定義 Draft 狀態(tài)因?yàn)檫@是我們希望博文的初始狀態(tài)。

當(dāng)創(chuàng)建新的 Post 時(shí),我們將其 state 字段設(shè)置為一個(gè)存放了 Box 的 Some 值。這個(gè) Box 指向一個(gè) Draft 結(jié)構(gòu)體新實(shí)例。這確保了無(wú)論何時(shí)新建一個(gè) Post 實(shí)例,它都會(huì)從草案開(kāi)始。因?yàn)?nbsp;Post 的 state 字段是私有的,也就無(wú)法創(chuàng)建任何其他狀態(tài)的 Post 了!。Post::new 函數(shù)中將 content 設(shè)置為新建的空 String。

存放博文內(nèi)容的文本

在示例 17-11 中,展示了我們希望能夠調(diào)用一個(gè)叫做 add_text 的方法并向其傳遞一個(gè) &str 來(lái)將文本增加到博文的內(nèi)容中。選擇實(shí)現(xiàn)為一個(gè)方法而不是將 content 字段暴露為 pub 。這意味著之后可以實(shí)現(xiàn)一個(gè)方法來(lái)控制 content 字段如何被讀取。add_text 方法是非常直觀的,讓我們?cè)谑纠?17-13 的 impl Post 塊中增加一個(gè)實(shí)現(xiàn):

文件名: src/lib.rs

impl Post {
    // --snip--
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }
}

示例 17-13: 實(shí)現(xiàn)方法 add_text 來(lái)向博文的 content 增加文本

add_text 獲取一個(gè) self 的可變引用,因?yàn)樾枰淖冋{(diào)用 add_text 的 Post 實(shí)例。接著調(diào)用 content 中的 String 的 push_str 并傳遞 text 參數(shù)來(lái)保存到 content 中。這不是狀態(tài)模式的一部分,因?yàn)樗男袨椴⒉灰蕾?lài)博文所處的狀態(tài)。add_text 方法完全不與 state 狀態(tài)交互,不過(guò)這是我們希望支持的行為的一部分。

確保博文草案的內(nèi)容是空的

即使調(diào)用 add_text 并向博文增加一些內(nèi)容之后,我們?nèi)匀幌M?nbsp;content 方法返回一個(gè)空字符串 slice,因?yàn)椴┪娜匀惶幱诓莅笭顟B(tài),如示例 17-11 的第 8 行所示?,F(xiàn)在讓我們使用能滿(mǎn)足要求的最簡(jiǎn)單的方式來(lái)實(shí)現(xiàn) content 方法:總是返回一個(gè)空字符串 slice。當(dāng)實(shí)現(xiàn)了將博文狀態(tài)改為發(fā)布的能力之后將改變這一做法。但是目前博文只能是草案狀態(tài),這意味著其內(nèi)容應(yīng)該總是空的。示例 17-14 展示了這個(gè)占位符實(shí)現(xiàn):

文件名: src/lib.rs

impl Post {
    // --snip--
    pub fn content(&self) -> &str {
        ""
    }
}

列表 17-14: 增加一個(gè) Post 的 content 方法的占位實(shí)現(xiàn),它總是返回一個(gè)空字符串 slice

通過(guò)增加這個(gè) content 方法,示例 17-11 中直到第 8 行的代碼能如期運(yùn)行。

請(qǐng)求審核博文來(lái)改變其狀態(tài)

接下來(lái)需要增加請(qǐng)求審核博文的功能,這應(yīng)當(dāng)將其狀態(tài)由 Draft 改為 PendingReview。示例 17-15 展示了這個(gè)代碼:

文件名: 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: 實(shí)現(xiàn) Post 和 State trait 的 request_review 方法

這里為 Post 增加一個(gè)獲取 self 可變引用的公有方法 request_review。接著在 Post 的當(dāng)前狀態(tài)下調(diào)用內(nèi)部的 request_review 方法,并且第二個(gè) request_review 方法會(huì)消費(fèi)當(dāng)前的狀態(tài)并返回一個(gè)新?tīng)顟B(tài)。

這里給 State trait 增加了 request_review 方法;所有實(shí)現(xiàn)了這個(gè) trait 的類(lèi)型現(xiàn)在都需要實(shí)現(xiàn) request_review 方法。注意不同于使用 self、 &self 或者 &mut self 作為方法的第一個(gè)參數(shù),這里使用了 self: Box<Self>。這個(gè)語(yǔ)法意味著該方法只可在持有這個(gè)類(lèi)型的 Box 上被調(diào)用。這個(gè)語(yǔ)法獲取了 Box<Self> 的所有權(quán)使老狀態(tài)無(wú)效化,以便 Post 的狀態(tài)值可轉(zhuǎn)換為一個(gè)新?tīng)顟B(tài)。

為了消費(fèi)老狀態(tài),request_review 方法需要獲取狀態(tài)值的所有權(quán)。這就是 Post 的 state 字段中 Option 的來(lái)歷:調(diào)用 take 方法將 state 字段中的 Some 值取出并留下一個(gè) None,因?yàn)?Rust 不允許結(jié)構(gòu)體實(shí)例中存在值為空的字段。這使得我們將 state 的值移出 Post 而不是借用它。接著我們將博文的 state 值設(shè)置為這個(gè)操作的結(jié)果。

我們需要將 state 臨時(shí)設(shè)置為 None 來(lái)獲取 state 值,即老狀態(tài)的所有權(quán),而不是使用 self.state = self.state.request_review(); 這樣的代碼直接更新?tīng)顟B(tài)值。這確保了當(dāng) Post 被轉(zhuǎn)換為新?tīng)顟B(tài)后不能再使用老 state 值。

Draft 的 request_review 方法需要返回一個(gè)新的,裝箱的 PendingReview 結(jié)構(gòu)體的實(shí)例,其用來(lái)代表博文處于等待審核狀態(tài)。結(jié)構(gòu)體 PendingReview 同樣也實(shí)現(xiàn)了 request_review 方法,不過(guò)它不進(jìn)行任何狀態(tài)轉(zhuǎn)換。相反它返回自身,因?yàn)楫?dāng)我們請(qǐng)求審核一個(gè)已經(jīng)處于 PendingReview 狀態(tài)的博文,它應(yīng)該繼續(xù)保持 PendingReview 狀態(tài)。

現(xiàn)在我們能看出狀態(tài)模式的優(yōu)勢(shì)了:無(wú)論 state 是何值,Post 的 request_review 方法都是一樣的。每個(gè)狀態(tài)只負(fù)責(zé)它自己的規(guī)則。

我們將繼續(xù)保持 Post 的 content 方法實(shí)現(xiàn)不變,返回一個(gè)空字符串 slice。現(xiàn)在我們可以擁有 PendingReview 狀態(tài)和 Draft 狀態(tài)的 Post 了,不過(guò)我們希望在 PendingReview 狀態(tài)下 Post 也有相同的行為?,F(xiàn)在示例 17-11 中直到 10 行的代碼是可以執(zhí)行的!

增加改變 content 行為的 approve 方法

approve 方法將與 request_review 方法類(lèi)似:它會(huì)將 state 設(shè)置為審核通過(guò)時(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 實(shí)現(xiàn) approve 方法

這里為 State trait 增加了 approve 方法,并新增了一個(gè)實(shí)現(xiàn)了 State 的結(jié)構(gòu)體,Published 狀態(tài)。

類(lèi)似于 PendingReview 中 request_review 的工作方式,如果對(duì) Draft 調(diào)用 approve 方法,并沒(méi)有任何效果,因?yàn)樗鼤?huì)返回 self。當(dāng)對(duì) PendingReview 調(diào)用 approve 時(shí),它返回一個(gè)新的、裝箱的 Published 結(jié)構(gòu)體的實(shí)例。Published 結(jié)構(gòu)體實(shí)現(xiàn)了 State trait,同時(shí)對(duì)于 request_review 和 approve 兩方法來(lái)說(shuō),它返回自身,因?yàn)樵谶@兩種情況博文應(yīng)該保持 Published 狀態(tài)。

現(xiàn)在需要更新 Post 的 content 方法。我們希望 content 根據(jù) Post 的當(dāng)前狀態(tài)返回值,所以需要 Post 代理一個(gè)定義于 state 上的 content 方法,如實(shí)例 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 方法來(lái)委托調(diào)用 State 的content 方法

因?yàn)槟繕?biāo)是將所有像這樣的規(guī)則保持在實(shí)現(xiàn)了 State 的結(jié)構(gòu)體中,我們將調(diào)用 state 中的值的 content 方法并傳遞博文實(shí)例(也就是 self)作為參數(shù)。接著返回 state 值的 content 方法的返回值。

這里調(diào)用 Option 的 as_ref 方法是因?yàn)樾枰?nbsp;Option 中值的引用而不是獲取其所有權(quán)。因?yàn)?nbsp;state 是一個(gè) Option<Box<dyn State>>,調(diào)用 as_ref 會(huì)返回一個(gè) Option<&Box<dyn State>>。如果不調(diào)用 as_ref,將會(huì)得到一個(gè)錯(cuò)誤,因?yàn)椴荒軐?nbsp;state 移動(dòng)出借用的 &self 函數(shù)參數(shù)。

接著調(diào)用 unwrap 方法,這里我們知道它永遠(yuǎn)也不會(huì) panic,因?yàn)?nbsp;Post 的所有方法都確保在他們返回時(shí) state 會(huì)有一個(gè) Some 值。這就是一個(gè)第十二章 “當(dāng)我們比編譯器知道更多的情況” 部分討論過(guò)的我們知道 None 是不可能的而編譯器卻不能理解的情況。

接著我們就有了一個(gè) &Box<dyn State>,當(dāng)調(diào)用其 content 時(shí),Deref 強(qiáng)制轉(zhuǎn)換會(huì)作用于 & 和 Box ,這樣最終會(huì)調(diào)用實(shí)現(xiàn)了 State trait 的類(lèi)型的 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 方法

這里增加了一個(gè) content 方法的默認(rèn)實(shí)現(xiàn)來(lái)返回一個(gè)空字符串 slice。這意味著無(wú)需為 Draft 和 PendingReview 結(jié)構(gòu)體實(shí)現(xiàn) content 了。Published 結(jié)構(gòu)體會(huì)覆蓋 content 方法并會(huì)返回 post.content 的值。

注意這個(gè)方法需要生命周期注解,如第十章所討論的。這里獲取 post 的引用作為參數(shù),并返回 post 一部分的引用,所以返回的引用的生命周期與 post 參數(shù)相關(guān)。

現(xiàn)在示例完成了 —— 現(xiàn)在示例 17-11 中所有的代碼都能工作!我們通過(guò)發(fā)布博文工作流的規(guī)則實(shí)現(xiàn)了狀態(tài)模式。圍繞這些規(guī)則的邏輯都存在于狀態(tài)對(duì)象中而不是分散在 Post 之中。

狀態(tài)模式的權(quán)衡取舍

我們展示了 Rust 是能夠?qū)崿F(xiàn)面向?qū)ο蟮臓顟B(tài)模式的,以便能根據(jù)博文所處的狀態(tài)來(lái)封裝不同類(lèi)型的行為。Post 的方法并不知道這些不同類(lèi)型的行為。通過(guò)這種組織代碼的方式,要找到所有已發(fā)布博文的不同行為只需查看一處代碼:Published 的 State trait 的實(shí)現(xiàn)。

如果要?jiǎng)?chuàng)建一個(gè)不使用狀態(tài)模式的替代實(shí)現(xiàn),則可能會(huì)在 Post 的方法中,或者甚至于在 main 代碼中用到 match 語(yǔ)句,來(lái)檢查博文狀態(tài)并在這里改變其行為。這意味著需要查看很多位置來(lái)理解處于發(fā)布狀態(tài)的博文的所有邏輯!這在增加更多狀態(tài)時(shí)會(huì)變得更糟:每一個(gè) match 語(yǔ)句都會(huì)需要另一個(gè)分支。

對(duì)于狀態(tài)模式來(lái)說(shuō),Post 的方法和使用 Post 的位置無(wú)需 match 語(yǔ)句,同時(shí)增加新?tīng)顟B(tài)只涉及到增加一個(gè)新 struct 和為其實(shí)現(xiàn) trait 的方法。

這個(gè)實(shí)現(xiàn)易于擴(kuò)展增加更多功能。為了體會(huì)使用此模式維護(hù)代碼的簡(jiǎn)潔性,請(qǐng)嘗試如下一些建議:

  • 增加 ?reject? 方法將博文的狀態(tài)從 ?PendingReview? 變回 ?Draft?
  • 在將狀態(tài)變?yōu)?nbsp;?Published? 之前需要兩次 ?approve? 調(diào)用
  • 只允許博文處于 ?Draft? 狀態(tài)時(shí)增加文本內(nèi)容。提示:讓狀態(tài)對(duì)象負(fù)責(zé)內(nèi)容可能發(fā)生什么改變,但不負(fù)責(zé)修改 ?Post?。

狀態(tài)模式的一個(gè)缺點(diǎn)是因?yàn)闋顟B(tài)實(shí)現(xiàn)了狀態(tài)之間的轉(zhuǎn)換,一些狀態(tài)會(huì)相互聯(lián)系。如果在 PendingReview 和 Published 之間增加另一個(gè)狀態(tài),比如 Scheduled,則不得不修改 PendingReview 中的代碼來(lái)轉(zhuǎn)移到 Scheduled。如果 PendingReview 無(wú)需因?yàn)樾略龅臓顟B(tài)而改變就更好了,不過(guò)這意味著切換到另一種設(shè)計(jì)模式。

另一個(gè)缺點(diǎn)是我們會(huì)發(fā)現(xiàn)一些重復(fù)的邏輯。為了消除他們,可以嘗試為 State trait 中返回 self 的 request_review 和 approve 方法增加默認(rèn)實(shí)現(xiàn),不過(guò)這會(huì)違反對(duì)象安全性,因?yàn)?trait 不知道 self 具體是什么。我們希望能夠?qū)?nbsp;State 作為一個(gè) trait 對(duì)象,所以需要其方法是對(duì)象安全的。

另一個(gè)重復(fù)是 Post 中 request_review 和 approve 這兩個(gè)類(lèi)似的實(shí)現(xiàn)。他們都委托調(diào)用了 state 字段中 Option 值的同一方法,并在結(jié)果中為 state 字段設(shè)置了新值。如果 Post 中的很多方法都遵循這個(gè)模式,我們可能會(huì)考慮定義一個(gè)宏來(lái)消除重復(fù)(查看第十九章的 “宏” 部分)。

完全按照面向?qū)ο笳Z(yǔ)言的定義實(shí)現(xiàn)這個(gè)模式并沒(méi)有盡可能地利用 Rust 的優(yōu)勢(shì)。讓我們看看一些代碼中可以做出的修改,來(lái)將無(wú)效的狀態(tài)和狀態(tài)轉(zhuǎn)移變?yōu)榫幾g時(shí)錯(cuò)誤。

將狀態(tài)和行為編碼為類(lèi)型

我們將展示如何稍微反思狀態(tài)模式來(lái)進(jìn)行一系列不同的權(quán)衡取舍。不同于完全封裝狀態(tài)和狀態(tài)轉(zhuǎn)移使得外部代碼對(duì)其毫不知情,我們將狀態(tài)編碼進(jìn)不同的類(lèi)型。如此,Rust 的類(lèi)型檢查就會(huì)將任何在只能使用發(fā)布博文的地方使用草案博文的嘗試變?yōu)榫幾g時(shí)錯(cuò)誤。

讓我們考慮一下示例 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)建一個(gè)新的草案博文,并能夠增加博文的內(nèi)容。不過(guò)不同于存在一個(gè)草案博文時(shí)返回空字符串的 content 方法,我們將使草案博文完全沒(méi)有 content 方法。這樣如果嘗試獲取草案博文的內(nèi)容,將會(huì)得到一個(gè)方法不存在的編譯錯(cuò)誤。這使得我們不可能在生產(chǎn)環(huán)境意外顯示出草案博文的內(nèi)容,因?yàn)檫@樣的代碼甚至就不能編譯。示例 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 和沒(méi)有 content 方法的 DraftPost

Post 和 DraftPost 結(jié)構(gòu)體都有一個(gè)私有的 content 字段來(lái)儲(chǔ)存博文的文本。這些結(jié)構(gòu)體不再有 state 字段因?yàn)槲覀儗顟B(tài)編碼改為結(jié)構(gòu)體類(lèi)型。Post 將代表發(fā)布的博文,它有一個(gè)返回 content 的 content 方法。

仍然有一個(gè) Post::new 函數(shù),不過(guò)不同于返回 Post 實(shí)例,它返回 DraftPost 的實(shí)例?,F(xiàn)在不可能創(chuàng)建一個(gè) Post 實(shí)例,因?yàn)?nbsp;content 是私有的同時(shí)沒(méi)有任何函數(shù)返回 Post。

DraftPost 上定義了一個(gè) add_text 方法,這樣就可以像之前那樣向 content 增加文本,不過(guò)注意 DraftPost 并沒(méi)有定義 content 方法!如此現(xiàn)在程序確保了所有博文都從草案開(kāi)始,同時(shí)草案博文沒(méi)有任何可供展示的內(nèi)容。任何繞過(guò)這些限制的嘗試都會(huì)產(chǎn)生編譯錯(cuò)誤。

實(shí)現(xiàn)狀態(tài)轉(zhuǎn)移為不同類(lèi)型的轉(zhuǎn)換

那么如何得到發(fā)布的博文呢?我們希望強(qiáng)制執(zhí)行的規(guī)則是草案博文在可以發(fā)布之前必須被審核通過(guò)。等待審核狀態(tài)的博文應(yīng)該仍然不會(huì)顯示任何內(nèi)容。讓我們通過(guò)增加另一個(gè)結(jié)構(gòu)體 PendingReviewPost 來(lái)實(shí)現(xiàn)這個(gè)限制,在 DraftPost 上定義 request_review 方法來(lái)返回 PendingReviewPost,并在 PendingReviewPost 上定義 approve 方法來(lái)返回 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 通過(guò)調(diào)用 DraftPost 的 request_review 創(chuàng)建,approve 方法將 PendingReviewPost 變?yōu)榘l(fā)布的 Post

request_review 和 approve 方法獲取 self 的所有權(quán),因此會(huì)消費(fèi) DraftPost 和 PendingReviewPost 實(shí)例,并分別轉(zhuǎn)換為 PendingReviewPost 和發(fā)布的 Post。這樣在調(diào)用 request_review 之后就不會(huì)遺留任何 DraftPost 實(shí)例,后者同理。PendingReviewPost 并沒(méi)有定義 content 方法,所以嘗試讀取其內(nèi)容會(huì)導(dǎo)致編譯錯(cuò)誤,DraftPost 同理。因?yàn)槲ㄒ坏玫蕉x了 content 方法的 Post 實(shí)例的途徑是調(diào)用 PendingReviewPost 的 approve 方法,而得到 PendingReviewPost 的唯一辦法是調(diào)用 DraftPost 的 request_review 方法,現(xiàn)在我們就將發(fā)博文的工作流編碼進(jìn)了類(lèi)型系統(tǒng)。

這也意味著不得不對(duì) main 做出一些小的修改。因?yàn)?nbsp;request_review 和 approve 返回新實(shí)例而不是修改被調(diào)用的結(jié)構(gòu)體,所以我們需要增加更多的 let post = 覆蓋賦值來(lái)保存返回的實(shí)例。也不再能斷言草案和等待審核的博文的內(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 中使用新的博文工作流實(shí)現(xiàn)的修改

不得不修改 main 來(lái)重新賦值 post 使得這個(gè)實(shí)現(xiàn)不再完全遵守面向?qū)ο蟮臓顟B(tài)模式:狀態(tài)間的轉(zhuǎn)換不再完全封裝在 Post 實(shí)現(xiàn)中。然而,得益于類(lèi)型系統(tǒng)和編譯時(shí)類(lèi)型檢查,我們得到了的是無(wú)效狀態(tài)是不可能的!這確保了某些特定的 bug,比如顯示未發(fā)布博文的內(nèi)容,將在部署到生產(chǎn)環(huán)境之前被發(fā)現(xiàn)。

嘗試為示例 17-20 之后的 blog crate 實(shí)現(xiàn)這一部分開(kāi)始所建議的增加額外需求的任務(wù)來(lái)體會(huì)使用這個(gè)版本的代碼是何感覺(jué)。注意在這個(gè)設(shè)計(jì)中一些需求可能已經(jīng)完成了。

即便 Rust 能夠?qū)崿F(xiàn)面向?qū)ο笤O(shè)計(jì)模式,也有其他像將狀態(tài)編碼進(jìn)類(lèi)型這樣的模式存在。這些模式有著不同的權(quán)衡取舍。雖然你可能非常熟悉面向?qū)ο竽J剑匦滤伎歼@些問(wèn)題來(lái)利用 Rust 提供的像在編譯時(shí)避免一些 bug 這樣有益功能。在 Rust 中面向?qū)ο竽J讲⒉豢偸亲詈玫慕鉀Q方案,因?yàn)?Rust 擁有像所有權(quán)這樣的面向?qū)ο笳Z(yǔ)言所沒(méi)有的功能。

總結(jié)

閱讀本章后,不管你是否認(rèn)為 Rust 是一個(gè)面向?qū)ο笳Z(yǔ)言,現(xiàn)在你都見(jiàn)識(shí)了 trait 對(duì)象是一個(gè) Rust 中獲取部分面向?qū)ο蠊δ艿姆椒ā?dòng)態(tài)分發(fā)可以通過(guò)犧牲少量運(yùn)行時(shí)性能來(lái)為你的代碼提供一些靈活性。這些靈活性可以用來(lái)實(shí)現(xiàn)有助于代碼可維護(hù)性的面向?qū)ο竽J健ust 也有像所有權(quán)這樣不同于面向?qū)ο笳Z(yǔ)言的功能。面向?qū)ο竽J讲⒉豢偸抢?Rust 優(yōu)勢(shì)的最好方式,但也是可用的選項(xiàng)。

接下來(lái),讓我們看看另一個(gè)提供了多樣靈活性的 Rust 功能:模式。貫穿全書(shū)的模式, 我們已經(jīng)和它們打過(guò)照面了,但并沒(méi)有見(jiàn)識(shí)過(guò)它們的全部本領(lǐng)。讓我們開(kāi)始探索吧!


以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)