Updated 'src/oop/implementing.md'.

This commit is contained in:
Hector PENG
2026-04-18 16:39:02 +08:00
parent 9234b1f0ea
commit a7221b719b
6 changed files with 75 additions and 112 deletions

6
projects/blog/Cargo.toml Normal file
View File

@@ -0,0 +1,6 @@
[package]
name = "blog"
version = "0.1.0"
edition = "2024"
[dependencies]

View File

@@ -1,6 +1,3 @@
#![allow(dead_code)]
#![allow(unused_variables)]
pub struct Post {
state: Option<Box<dyn State>>,
content: String,
@@ -26,19 +23,18 @@ impl Post {
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())
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 { "" }
trait State {
fn request_review(self: Box<Self>) -> Box<dyn State>;
fn approve(self: Box<Self>) -> Box<dyn State>;
}
}
struct Draft {}
@@ -75,8 +71,4 @@ impl State for Published {
fn approve(self: Box<Self>) -> Box<dyn State> {
self
}
fn content<'a>(&self, post: &'a Post) -> &'a str {
&post.content
}
}

View File

@@ -1,8 +0,0 @@
[package]
name = "simple_blog"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@@ -1,17 +0,0 @@
#![allow(dead_code)]
#![allow(unused_variables)]
use simple_blog::Post;
fn main() {
let mut post = Post::new();
post.add_text("今天午饭我吃了沙拉。");
assert_eq! ("", post.content());
post.request_review();
assert_eq! ("", post.content());
post.approve();
assert_eq! ("今天午饭我吃了沙拉。", post.content());
}

View File

@@ -117,7 +117,7 @@
- [面向对象编程特性](Ch17_Object_Oriented_Programming_Features_of_Rust.md)
- [面向对象语言的特征](oop/characteristics_oop.md)
- [使用特质对象抽象共用行为](oop/trait_objects.md)
- [实现一种面向对象设计模式](oop/implementing.md)
- [实现面向对象设计模式](oop/implementing.md)
- [模式与匹配](Ch18_Patterns_and_Matching.md)
- [可使用模式的全部处所](patterns/all_places.md)

View File

@@ -1,30 +1,34 @@
# 实现面向对象的设计模式
*状态模式the state pattern* 属于一种面向对象设计模式。这种模式的核心,便是咱们要定义某个值在其内部可能有的一套各种状态。这些状态由一 *状态对象state objects* 表示的,同时该值的行为会根据其状态而改变。接下来咱们会完成有着保存其状态字段,该字段将有着“草稿draft”、“审阅review” 或“已发布published” 三状态集合的状态对象,的一个博客帖子结构体示例
*状态模式the state pattern*属于一种面向对象设计模式。这种模式的关键在于,我们定义某个值可能在内部具有的一套状态。这些状态由一 *状态对象state objects* 表示,而值的行为会根据其状态而变化。我们即将进行一个博客帖子结构体的示例,其有着一个保存其状态字段,该字段将“草稿”、“审阅” 或 “已发布” 三状态集中的一个状态对象
状态对象共用功能:当然,在 Rust 中咱们会使用结构体与特质,而非对象与继承。每个状态对象负责自己的行为,以及在其应变换为另一状态时自身的治理。保存状态对象的值,对这些状态的不同行为,或这些状态之间何时变换就毫不知情
状态对象共用功能:当然,在 Rust 中,我们使用结构体与特质,而非对象与继承。每个状态对象负责自己的行为,并管理何时应转换为另一状态。保存状态对象的值,对状态的不同行为,或何时进行状态转换一无所知
用状态模式的优势在于,当程序的业务需求改变时,咱们将不需要修改该值保存状态的那些代码,也不需要修改用到该值的那些代码。们只需更新某个状态对象内部的那些代码,来改变其规则,或添加别的一些状态对象。
使用状态模式的优势在于,当程序的业务需求发生变化时,我们无需修改保存状态的值的代码,用到该值的代码。们只需更新某个状态对象内部的代码即可更改其规则,或添加更多状态对象。
首先,们将以更传统的面向对象方式实现这种状态模式,随后咱们将使用对于 Rust 中更自然一些的方法。下面就来深入到使用状态模式,逐步实现一个博客帖子工作流。
首先,们将以更传统的面向对象方式实现状态模式,然后,我们将使用一种在 Rust 中更自然的方式。我们来深入研究如何使用状态模式,逐步实现博客帖子工作流。
最终功能看起来像下面这样:
最终功能看起来像下面这样:
1. 博客帖子作为空白的草稿开始;
2. 草稿完成后,对帖子的审阅是必需的;
3. 帖子被批准后,其得以发布;
4. 只有已发布的博客帖子才会返回用以打印的内容,从而未获批准的帖子不会意外发布。
对帖子尝试的任何其他修改均应无效。例如,当我们在请求审核之前尝试批准博客帖子草稿时,该帖子应保持为未发布的草稿。
1. 博客帖子以一个空的草稿开始;
2. 在草稿写好后,该帖子就要求审阅一下;
3. 在帖子被批准后,其就会被发布;
4. 只有发布了的帖子,才会返回要打印的内容,因此那些未获批准的帖子就不会被无故发布。
## 尝试传统的面向对象风格
为了解决同一个问题,代码的结构方式有无数种,每种都有不同的权衡取舍。这一节的实现更多的是传统的面向对象风格,虽然在 Rust 中可以这样编写,但并未利用 Rust 的某些优势。稍后,我们将演示一种不同的解决方案,他虽然仍采用面向对象的设计模式,但其结构方式对于有面向对象编程经验的开发者而言,可能显得不太熟悉。我们将比较这两种方案,以体验到与其他语言的代码相比,以不同方式设计 Rust 代码的权衡取舍。
所有别的在帖子上的尝试修改,都应无效。比如,在完成审阅之前,若咱们尝试批准博客帖子草稿,那么该帖子应保持为一个未发布的草稿
下面清单 17-11 给出了代码形式的这个工作流:此为咱们将在一个名为 `simple_blog` 的库代码箱中,实现的这个 API 的一个示例用法。由于咱们尚未实现该 `simple_blog` 代码箱,因此这段代码尚不会编译。
下面清单 18-11 以代码形式展示了这一工作流程:这是我们将在名为 `blog` 的库代码箱中实现的 API 的一个使用示例。这还不会编译,因为我们尚未实现 `blog` 代码箱
<a name="listing_18-11"></a>
文件名:`src/main.rs`
```rust
use simple_blog::Post;
use blog::Post;
fn main() {
let mut post = Post::new();
@@ -40,24 +44,22 @@ fn main() {
}
```
*清单 17-11:验证咱们打算这个 `simple_blog` 代码箱要有的必要功能的代码*
**清单 18-11**:演示我们希望 `blog` 代码箱具备的预期行为的代码
咱们打算运行用户使用 `Post::new` 创建出一个新的帖子草稿。们打算允许将一些文本添加到博客帖子。当咱们在审批之前,尝试立即获取帖子的内容时,由于该帖子仍为一个草稿,因此咱们就不应得到任何文本。出于验证目的,咱们已在该代码中添加了 `assert_eq!`而为此目的的良好单元测试,就应断言帖子草稿会从那个 `content` 方法返回空字符串,而咱们并未打算为此示例编写一些测试。
我们希望允许用户使用 `Post::new`创建新的博客帖子草稿。们打算允许添加文本到博客帖子。当我们尝试在审批之前立即获取帖子的内容时,我们不应得到任何文本,因为该帖子仍然是草稿。出于演示目的,我们在代码中添加了 `assert_eq!`针对这点的理想单元测试,断言帖子草稿会从 `content` 方法返回空字符串,但我们不会为这个示例编写测试。
接下来,咱们打算开启该帖子的审阅请求,同时咱们系统在等待审阅期间,`content` 返回一个空字符串。在该帖子得到审批时,他应得以发布了,表示在 `content` 被调用时,该帖子的文将被返回。
接下来,我们希望启用对帖子的一次审阅请求,并且我们希望在等待审阅期间,`content` 返回空字符串。当帖子获得批准后,他应得以发布,这意味着当 `content` 被调用时,该帖子的文将被返回。
请注意咱们与 `simple_blog` 代码箱交互的唯一类型,便`Post` 这个类型。类型将用状态模式,并保存着将为表示帖子可能状态 -- 草稿、等待审阅或已发布,的三个状态对象之一的一个值。从一种状态改变为另一状态,将在 `Post` 类型得以内部管理。这些状态,会因应着库用户 `Post` 实例的方法调用而改变,但用户们却不必直接管理这些状态改变。同样,用户们是无法在这些状态上犯下错误的,比如在帖子未审阅前发布帖子。
请注意,我们与该代码箱交互的唯一类型是 `Post` 类型。这个类型将使用状态模式,并保存一个值,该值将是三个状态对象之一,表示帖子可能出于的不同状态 -- 草稿、等待审阅或已发布。从一种状态另一状态的更改,将在 `Post` 类型内部得以内部管理。状态的变化,是响应于库用户 `Post` 实例调用的方法而发生的,但用户不必直接管理状态变更。此外,用户也不会在状态方面犯错,比如在审阅前发布帖子。
## 定义 `Post` 并创建出一个草稿状态的新实例
### 定义 `Post` 并创建新实例
**Defining `Post` and Creating a New Instance in the Draft State**
我们来开始库的实现!我们知道我们需要一个公开的 `Post` 结构体保存一些内容,因此我们将从该结构体的定义,及用于创建 `Post` 实例的关联公开 `new` 函数开始,如下清单 18-12 中所示。我们还将构造一个私有的 `State` 特质,将定义某个 `Post` 的所有状态对象必须具备的行为。
然后,`Post` 类型将在名为 `state` 的私有字段的 `Option<T>` 值内,保存 `Box<dyn State>` 的特质对象,以保存状态对象。稍后咱们就会明白为何 `Option<T>` 是必要的。
下面就来开始这个库的实现!咱们清楚咱们需要保存着一些内容的一个公开的 `Post` 结构体,因此咱们将以这个结构体的定义,及创建出 `Post` 实例的一个关联的公开 `new` 函数开始,如下清单 17-12 中所示。咱们还将构造出将定义 `Post`的全部状态对象,所必须有的行为的一个私有 `State` 特质。
随后 `Post` 类型将在名为 `state` 的私有字段的 `Option<T>` 值内部,保存 `Box<dyn State>` 类型的一个特质对象,来保存状态对象。过一会儿,咱们就会看到为何那个 `Option<T>` 是必要的。
<a name="listing_18-12"></a>
文件名:`src/lib.rs`
```rust
@@ -82,75 +84,65 @@ struct Draft {}
impl State for Draft {}
```
*清单 17-12`Post` 结构体的定义,以及创建新 `Post` 实例的 `new` 函数、`State` 特质,以及 `Draft` 结构体*
**清单 18-12**`Post` 结构体的定义,创建新 `Post` 实例的 `new` 函数、`State` 特质,以及 `Draft` 结构体
其中 `State` 特质定义出由不同帖子状态共用的行为。这些状态对象分别是 `Draft``PendingReview` `Published`同时他们都将实现 `State` 特质。至于现在,这特质并无任何方法,而由于 `Draft` 状态是咱们想要帖子开始的状态,因此咱们将以仅定义出这个状态开始
其中 `State` 特质定义不同帖子状态共用的行为。状态对象分别是 `Draft``PendingReview` `Published`,他们都将实现 `State` 特质。目前,这特质没有任何方法,且我们以仅定义 `Draft` 状态开始,因为这是我们的帖子开始的状态
创建新的 `Post` 实例时,咱们将`state` 自动设置为了保存着一个 `Box` 值的 `Some` 值。这个 `Box` 会只想 `Draft` 结构体的一个新实例。这确保不能在咱们何时创建一个 `Post` 的新实例,其都将作为一篇草稿开始。由于 `Post``state` 字段是私有的,因此没有办法创建出其他任何状态的一个 `Post`!在 `Post::new` 函数中,咱们把 `content` 字段设置为了一个新的、空 `String`
当我们创建新的 `Post` 实例时,我们设置`state` 字段为包含一个 `Box` 值的 `Some` 值。这个 `Box` 值指向一个 `Draft` 结构体的新实例。这确保了每当我们创建一个 `Post` 的新实例时,他都将以草稿形式开始。由于 `Post``state` 字段是私有的,因此没有办法创建处于任何其他状态的 `Post`!在 `Post::new` 函数中,我们设置 `content` 字段一个新的、空 `String`
## 存储帖子内容文本
### 存储帖子内容文本
**Storing the Text of the Post Content**
在 17-11 中,咱们曾看到咱们希望能调用一个名为 `add_text` 的方法,并传递给他随后被作为博客帖子内容而添加的一个 `&str`。咱们将这实现为一个方法,而不是把 `content` 作为 `pub` 暴露出来,如此稍后咱们就可以实现一个将控制 `content` 字段数据如何被读取的方法。这个 `add_text` 方法是相当直截了当的,那么接下就来在清单 17-13 中,添加这个实现到 `impl Post` 代码块:
我们在清单 18-11 中看到,我们希望能够调用一个名为 `add_text` 的方法,并传递给他一个 `&str`,然后添加为博客帖子的文本内容。我们会作为方法实现这点,而不是暴露 `content` 字段为 `pub`,以便稍后我们可以实现一个方法,其将控制 `content` 字段的数据被读取的方式。`add_text` 方法相当简单,因此我们来添加下面清单 18-13 中的实现到 `impl Post` 代码块。
<a name="listing_18-13"></a>
文件名:`src/lib.rs`
```rust
impl Post {
// -- 跳过代码 --
pub fn add_text(&mut self, text: &str) {
self.content.push_str(text);
}
}
```
*清单 17-13:实现将文本添加到帖子 `content` 字段的 `add_text` 方法*
**清单 18-13**:实现 `add_text` 方法,以添加文本到帖子的 `content` 字段
由于咱们是在修改咱们正于其上调用 `add_text``Post` 实例,因此这个 `add_text` 方法便取了到 `self` 的可变引用。咱们随后调用了 `content` 字段中 `String` 类型上的 `push_str`并传递 `text` 参数添加到那个已保存的 `content`行为不依赖帖子所处的状态,因此其并非状态模式的一部分。这个 `add_text` 方法完全不与 `state` 字段交互,但其为咱们打算支持行为的一部分。
`add_text` 方法取到 `self` 的可变引用,因为我们正在修改我们正对其调用 `add_text``Post` 实例。然后,我们对 `content` 字段中 `String` 值调用 `push_str` 并传递 `text` 参数,以添加到已保存的 `content`这一行为不依赖帖子所处的状态,因此他不是状态模式的一部分。`add_text` 方法完全不与 `state` 字段交互,但他是我们希望支持行为的一部分。
## 确保帖子草稿的内容为空
### 确保草稿帖子的内容为空
**Ensuring the Content of a Draft Post Is Empty**
即使咱们已调用了 `add_text` 并把一些内容添加到了咱们的帖子,但由于该帖子仍处于草稿状态,故咱们仍想要那个 `content` 方法返回空字符串切片an empty string slice正如清单 17-11 中第 7 行所给出的那样。那么现在,就来用将满足此要求的最简单物件,实现这个 `content` 方法:即总是返回一个空字符串切片。稍后一旦咱们实现修改帖子状态的能力,从而帖子可被发布,咱们就会修改这个方法。到目前为止,贴子就只能处于草稿状态,因此帖子内容应始终为空。下面清单 17-14 给出了这种占位的实现:
即使我们调用了 `add_text` 并添加了一些内容到帖子,我们仍然希望 `content` 方法返回一个空的字符串切片,因为帖子仍处于草稿状态,正如清单 18-11 中的第一个 `assert_eq!` 所示。目前,我们来以将满足这一要求的最简单方式实现 `content` 方法:始终返回一个空字符串切片。一旦稍后实现修改帖子的状态以便其可被发布的能力后,我们将修改这个方法。到目前为止,贴子只能处于草稿状态,因此帖子内容应始终为空。下面清单 18-14 展示了这一占位符实现, a placehoder implementation。
<a name="listing_18-14"></a>
文件名:`src/lib.rs`
```rust
impl Post {
// -- 跳过代码 --
pub fn content(&self) -> &str {
""
}
}
```
*清单 17-14:添加始终返回空字符串切片的 `Post` 上 `content` 方法的一个占位实现,a placeholder implementation*
**清单 18-14**:为 `Post``content` 方法添加占位实现,其始终返回一个空字符串切片
有了添加的这个 `content` 方法,清单 17-11 中到第 7 行为止的那些代码就都将如预期那样工作了
通过这个添加的 `content` 方法,清单 18-11 中到第一个 `assert_eq!` 的所有代码都会按预期运行
## 请求帖子审阅改变状态
**Requesting a Review of the Post Changes Its State**
接下来,咱们就需要添加请求帖子审阅的功能了,帖子审阅应将其状态从 `Draft` 改变为 `PendingReview`。下面清单 17-15 给出了这样的代码:
### 请求审阅改变帖子的状态
接下来,我们需要添加请求对帖子审阅的功能,这应该将其状态从 `Draft` 更改为 `PendingReview`。下面清单 18-15 展示了这一代码。
<a name="listing_18-15"></a>
文件名:`src/lib.rs`
```rust
impl Post {
// -- 跳过代码 --
pub fn request_review(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.request_review())
@@ -179,36 +171,33 @@ impl State for PendingReview {
}
```
*清单 17-15实现 `Post` 与 `State` 特质上的 `request_review` 方法*
**清单 18-15**:实现 `Post``State` 特质上的 `request_review` 方法
们给 `Post` 名为 `request_review`一个公开方法,其将取到 `self`一个可变引用。随后咱们调用了 `Post` 当前状态上的内部 `request_review` 方法,而这第二个 `request_review` 方法会消费当前状态并返回一个新状态。
们给 `Post` 一个名为 `request_review` 的公开方法,其将取一个`self` 的可变引用。然后,我们对 `Post` 当前状态调用内部 `request_review` 方法,而这第二个 `request_review` 方法会消费当前状态并返回一个新状态。
咱们把那个 `request_review` 方法添加到了 `State` 特质;所有实现这个特质的类型,现在都将需要实现这个 `request_review` 方法。请注意这里用的不再是 `self``&self``&mut self` 作为方法的个参数,这里用的是 `self: Box<Self>`。这样的语法表示,在有在某个保存了该类型的 `Box` 调用时,这个方法才是有效。这种语法会取得 `Box<Self>` 的所有权,令到原有状态失效,进而 `Post` 的状态值可以转换到一种新的状态。
我们添加 `request_review` 方法 `State` 特质;所有实现这个特质的类型,现在都将需要实现 `request_review` 方法。请注意,我们没有使用 `self``&self``&mut self` 作为这个方法的第一个参数,而是使用 `self: Box<Self>`。这种语法意味着,这个方法仅在对包含这种类型的 `Box` 调用时有效。这种语法会取得 `Box<Self>` 的所有权,从而使旧状态失效,以便 `Post` 的状态值可以转换为新状态。
为了消费原有状态,这个 `request_review` 方法需要取得状态值的所有权。这便`Post`那个 `state` 字段中 `Option` 进入之处:咱们调用 `take` 方法(属于标准库的 `Option` 类型),来从 `state` 字段取出那个 `Some` 值,并由于 Rust 不允许们在结构体中有无效或空字段Rust doesn't let us have unpopulated fields in structs而在 `state` 字段中留下一个 `None`。这样就允许咱们把其中的 `state` 值,迁移出 `Post`而非借用他。随后咱们将把帖子的 `state`,设置为此操作的结果。
为了消费旧的状态,`request_review` 方法需要取得状态值的所有权。这`Post``state` 字段中 `Option` 发挥作用的地方:我们调用 `take` 方法,来从 `state` 字段取出 `Some` 值,并在其位置留下一个 `None`,因为 Rust 不允许们在结构体中有着未填充的(无效或空字段) 字段。这让我们可以从 `Post` 中迁出 `state` 值,而不是借用他。然后,我们将设置帖子的 `state`为这一操作的结果。
为了获取到 `state` 值的所有权,咱们就需要暂时 `state` 设置`None`,而非直接使用像是 `self.state = self.state.request_review();` 这样的代码设置他。这样做确保了在咱们已`Post` 转换为新状态后,无法使用原先`state` 值。
为了获 `state` 值的所有权,我们需要暂时设置 `state``None`,而不是以 `self.state = self.state.request_review();` 这样的代码直接设置他。这确保了在我们`Post` 转换为新状态后,`Post` 无法使用`state` 值。
`Draft` 上的 `request_review` 方法返回的是个新的、新加入的 `PendingReview` 装箱过的实例,表示帖子等待审阅时的状态。那个 `PendingReview` 结构体同样实现了 `request_review` 方法,但并未进行任何转换。而是,由于在咱们于某个已处于 `PendingReview` 状态的帖子上,请求审阅时,帖子应保持处于 `PendingReview` 状态,因此 `PendingReview``request_review` 方法调用返回的是他自己
`Draft` 上的 `request_review` 方法返回个新的、装箱后的`PendingReview` 结构体实例,表示帖子等待审阅时的状态。`PendingReview` 结构体实现了 `request_review` 方法,但不执行任何转换。相反,他会返回自身,因为当我们对已处于 `PendingReview` 状态的帖子请求审阅时,应保持处于 `PendingReview` 状态。
现在咱们就可以开始看到状态模式的优势了:不论 `Post``state` 值为何,其上的 `request_review` 方法都一样。每种状态都负责着其自己的那些规则。
现在我们可以开始看到状态模式的优势了:不论 `Post``state` 值为何,其上的 `request_review` 方法都一样。每种状态都负责自己的规则。
们将保 `Post` 上的 `content` 方法如其现在这样,即返回一个空字符串切片。现在咱们就可以让某个 `Post`处于 `PendingReview` 状态抑或 `Draft` 状态了,不过咱们想要 `PendingReview` 状态的同样行为。现在清单 17-11 到第 10 行便工作了!
们将保 `Post` 上的 `content` 方法不变,使其返回一个空字符串切片。我们现在既可以让 `Post` 处于 `PendingReview` 状态,也可以处于 `Draft` 状态,但我们想要 `PendingReview` 状态的同样行为。现在清单 18-11 再第二个 `assert_eq!` 前都可以正常工作了!
## 添加 `approve` 修改 `content` 的行为
## 添加 `approve` 修改 `content` 的行为
**Adding `approve` to Change the Behavior of `content`**
`approve` 方法将与 `request_review` 方法类似:他将把 `state` 设置为在帖子状态为 “批准” 时,当前状态所应表明的值,如下清单 17-16 中所示:
`approve` 方法将类似于 `request_review` 方法:他将设置 `state` 为当前状态规定的,再状态为 “批准” 时应具有的值,如下清单 18-16 中所示。
<a name="listing_18-16"></a>
文件名:`src/lib.rs`
```rust
impl Post {
// -- 跳过代码 --
pub fn approve(&mut self) {
if let Some(s) = self.state.take() {
self.state = Some(s.approve())
@@ -254,29 +243,30 @@ impl State for Published {
}
```
*清单 17-16实现 `Post` 与 `State` 特质上的 `approve` 方法*
**清单 18-16**:实现 `Post``State` 特质上的 `approve` 方法
咱们把这个 `approve` 方法,添加到了 `State` 特质,并添加一个实现 `State` 的新结构体,即 `Published` 状态。
我们添加 `approve` 方法 `State` 特质,并添加一个实现 `State` 的新结构体,即 `Published` 状态。
`PendingReview` `request_review` 工作方式类似,在咱们调用 `Draft` 上的 `approve` 方法时,由于 `approve` 将返回 `self`,因此他将没有效果。当咱们在 `PendingReview` 调用 `approve` 时,他返回的是一个新的、装箱后的 `Published` 结构体实例。这个 `Published` 结构体实现了 `State` 特质,而由于帖子在`request_review``approve` 两个方法,都应保持处于 `Published` 状态,因此对于这两个方法,他都会返回他本身
`PendingReview``request_review` 工作方式类似,当我们对 `Draft` 调用 `approve` 方法时,他将不会产生效果,因为 `approve` 将返回 `self`。在我们对 `PendingReview` 调用 `approve` 时,他返回一个新的、装箱后的 `Published` 结构体实例。`Published` 结构体实现了 `State` 特质,而对于 `request_review``approve` 两个方法,他都会返回自身,因为在这些情形下,帖子都应保持处于 `Published` 状态。
现在咱们就需要更新 `Post` 上的那个 `content` 方法了。咱们希望 `content` 返回的值取决于 `Post` 的当前状态,因此咱们就`Post`委托给定义在其 `state` 上的一个 `content` 方法,如下清单 17-17 中所示
现在我们需要更新 `Post` 上的 `content` 方法。我们希望 `content` 返回的值取决于 `Post` 的当前状态,因此我们即将`Post` 委托给定义在其 `state` 上的 `content` 方法,如下清单 18-17 中所示
<a name="listing_18-17"></a>
文件名:`src/lib.rs`
```rust
impl Post {
// -- 跳过代码 --
pub fn content(&self) -> &str {
self.state.as_ref().unwrap().content(self)
}
// -- 跳过代码 --
}
```
*清单 17-17:将 `Post` 上的 `content` 方法,更新为委托给 `State` 上的 `content` 方法*
**清单 18-17**:更新 `Post` 上的 `content` 方法为委托给 `State` 上的 `content` 方法
> **译注**:这里涉及到一个 “委托”、“委派” 的概念updating the `content` on `Post` to delegate to a `content` medthod on `State`
由于咱们的目标是要把所有规则,都保持于实现了 `State` 的那些结构体中,因此咱们就要调用 `state` 字段中值上的 `content` 方法,并将帖子实例(那就是 `self`)作为参数加以传递。随后咱们要返回从 `state` 值上的 `content` 方法调用所返回的值。