实现面向对象的设计模式

状态模式是一种面向对象的设计模式。关键 pattern 的含义是我们定义了一个值内部可以具有的一组状态。这 状态由一组状态对象表示,值的行为 根据其状态进行更改。我们将通过一个博客示例 post 结构体,该结构体具有一个用于保存其状态的字段,该 state 对象 从设置“草稿”、“审阅”或“已发布”。

state 对象共享功能:当然,在 Rust 中,我们使用 structs 和 traits 而不是 objects 和 inheritance。每个 state 对象都负责 为了它自己的行为,并控制它何时应该变成另一个 州。保存 state 对象的值对不同的 状态的行为或何时在状态之间转换。

使用状态模式的优点是,当业务 要求更改,则不需要更改 value 来保存状态或使用该值的代码。我们只需要 更新其中一个 State 对象中的代码以更改其规则,或者 添加更多 State 对象。

首先,我们将以更传统的 面向对象的方式,那么我们将使用一种更自然的方法 锈。让我们深入研究如何使用 state 模式。

最终功能将如下所示:

  1. 博客文章开始时是一个空草稿。
  2. 草稿完成后,请求对帖子进行审核。
  3. 帖子获得批准后,它就会被发布。
  4. 只有已发布的博客文章才会返回内容进行打印,因此未经批准的文章无法返回 意外发布。

尝试对帖子进行的任何其他更改都不应产生任何影响。例如,如果我们 在我们请求审核之前尝试批准草稿博客文章,该帖子 应该保持未发布的草稿。

示例 17-11 以代码形式展示了这个工作流:这是 API 中,我们将在名为blog.这还不会编译 因为我们还没有实现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:演示所需 行为,我们希望blogcrate 来拥有

我们希望允许用户使用Post::new.我们 希望允许将文本添加到博客文章中。如果我们尝试获取帖子的 内容,在批准之前,我们不应该获取任何文本,因为 Post 仍为 Draft。我们添加了assert_eq!在用于演示的代码中 目的。一个很好的单元测试是断言草稿博客 post 从content方法,但我们不会 为此示例编写测试。

接下来,我们想要启用对帖子的审核请求,并且我们希望content以在等待审核时返回空字符串。当帖子 获得批准,它应该被发布,这意味着帖子的文本将 在以下情况下返回content被调用。

请注意,我们从 crate 中交互的唯一类型是Post类型。此类型将使用状态模式,并保存一个值为 三个状态对象之一,表示帖子可以达到的各种状态 In - 草稿、等待审核或已发布。从一种状态更改为另一种状态 将在Post类型。状态在 响应我们库的用户在Post实例 但他们不必直接管理 state 更改。此外,用户不能 在各州出错,比如在文章被审核之前发布它。

定义Post和在 Draft 状态下创建新实例

让我们开始实施该库吧!我们知道我们需要一个 公共Poststruct 中,因此我们将从 结构体和关联的 public 的定义new函数创建 实例Post,如示例 17-12 所示。我们还会将私有的Statetrait 中定义Post必须有。

然后Post将持有Box<dyn State>Option<T>在名为state以保存 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:一个Poststruct 和new函数创建一个新的Post实例、Statetrait 和Draft结构

Statetrait 定义了不同 post 状态共享的行为。这 state 对象是Draft,PendingReviewPublished,并且它们都将 实现State特性。目前,trait 没有任何方法,并且 我们首先只定义Draftstate 的 URL,因为那是我们 想要一个帖子开始。

当我们创建一个新的Post,我们将其statefield 设置为Some值 持有Box.这Box指向Draft结构。 这确保了每当我们创建一个新的Post,它将以 草稿。因为state字段为Post是私有的,没有办法 创建一个Post在任何其他州!在Post::new函数,我们将contentfield 转换为新的空String.

存储文章内容的文本

我们在示例 17-11 中看到,我们希望能够调用一个名为add_text并传递一个&str,然后添加为 博客文章。我们将其作为方法实现,而不是公开contentfield 设置为pub,以便稍后我们可以实现一个方法,该方法将控制 这content读取 field 的数据。这add_textmethod 很漂亮 简单明了,那么让我们将示例 17-13 中的实现添加到impl Post块:

文件名: src/lib.rs

pub struct Post {
    state: Option<Box<dyn State>>,
    content: String,
}

impl Post {
    // --snip--
    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);
    }
}

trait State {}

struct Draft {}

impl State for Draft {}

示例 17-13:实现add_text添加方法 text 添加到 Post 的content

add_textmethod 接受对self,因为我们是 更改Post实例add_text上。然后我们调用push_strStringcontent并传递text要添加到 的参数 已保存的content.此行为不取决于帖子所处的状态, 所以它不是 state 模式的一部分。这add_text方法不交互 使用statefield 中,但它是我们想要的行为的一部分 支持。

确保草稿帖子的内容为空

即使在我们调用add_text并在我们的帖子中添加了一些内容,我们仍然 想要content方法返回一个空字符串 slice,因为 POST 是 仍然处于 draft 状态,如示例 17-11 的第 7 行所示。现在,让我们 实现content方法替换为最简单的东西来实现这个 要求:始终返回空字符串 slice。我们稍后会更改此设置 一旦我们实现了更改帖子状态以便可以发布的功能。 到目前为止,帖子只能处于 draft 状态,因此 post 内容应始终 为空。示例 17-14 显示了这个占位符实现:

文件名: src/lib.rs

pub struct Post {
    state: Option<Box<dyn State>>,
    content: String,
}

impl Post {
    // --snip--
    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 {
        ""
    }
}

trait State {}

struct Draft {}

impl State for Draft {}

示例 17-14:为 这contentmethod 开启Post这总是返回一个空的字符串 slice

添加此content方法,示例 17-11 中的所有内容到第 7 行 按预期工作。

请求对帖子的审核会更改其状态

接下来,我们需要添加请求对帖子进行审核的功能,这应该 将其状态从DraftPendingReview.示例 17-15 显示了这段代码:

文件名: src/lib.rs

pub struct Post {
    state: Option<Box<dyn State>>,
    content: String,
}

impl Post {
    // --snip--
    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())
        }
    }
}

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:实现request_review方法PostState特性

我们给予Post一个名为request_review这将需要一个 mutable 参考self.然后我们调用内部的request_review方法上的 的当前状态Post和这第二个request_review方法使用 current state 并返回一个新 state。

我们将request_review方法添加到State特性;所有类型的 implement 的 trait 现在需要实现request_review方法。 请注意,而不是self,&self&mut self作为第一个 parameter 中,我们有self: Box<Self>.此语法表示 方法仅在Box持有 type。此语法采用 所有权Box<Self>,使旧 state 无效,因此Post可以转换为新状态。

要使用旧状态,request_reviewmethod 需要获得所有权 的状态值。这就是Optionstate字段为Post进来:我们调用take方法获取Some值从state字段并留下一个None取而代之的是 Rust 不允许我们拥有 结构中未填充的字段。这样我们就可以将state值 out ofPost而不是借用它。然后我们将设置帖子的state值设置为 此作的结果。

我们需要设置stateNone临时设置,而不是直接设置 使用类似self.state = self.state.request_review();获取 这state价值。这确保了Post不能使用旧的state值 我们已将其转变为新状态。

request_reviewmethod 开启Draft返回一个新的PendingReviewstruct 的 API API 中,它表示 Post 等待 回顾。这PendingReviewstruct 还实现了request_review方法 但不执行任何转换。相反,它会返回自身,因为当我们 请求对PendingReviewstate 中,它应该保持 在PendingReview州。

现在我们可以开始看到 state 模式的优势:request_reviewmethod 开启Post无论其state价值。每 state 对自己的规则负责。

我们将保留contentmethod 开启Post按原样返回一个空字符串 片。我们现在可以有一个PostPendingReviewstate 以及Draftstate 中,但我们希望在PendingReview州。 示例 17-11 现在可以工作到第 10 行了!

添加approve以更改content

approve方法将类似于request_review方法:它会 设置state设置为 current state 表示它应该具有的值,当 state 被批准,如示例 17-16 所示:

文件名: src/lib.rs

pub struct Post {
    state: Option<Box<dyn State>>,
    content: String,
}

impl Post {
    // --snip--
    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 {
    // --snip--
    fn request_review(self: Box<Self>) -> Box<dyn State> {
        Box::new(PendingReview {})
    }

    fn approve(self: Box<Self>) -> Box<dyn State> {
        self
    }
}

struct PendingReview {}

impl State for PendingReview {
    // --snip--
    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
    }
}

示例 17-16:实现approvemethod 开启PostState特性

我们将approve方法添加到Statetrait 并添加一个新的结构体,该 实现StatePublished州。

类似于request_reviewPendingReview有效,如果我们调用approve方法在Draft,它不会有任何效果,因为approve将 返回self.当我们调用approvePendingReview,它会返回一个新的 boxed 实例的Published结构。这Publishedstruct 实现Statetrait 的request_review方法和approve方法,它会返回自身,因为 post 应该保留在Published在这些情况下。

现在我们需要更新contentmethod 开启Post.我们想要价值 返回自content依赖于Post,所以我们是 将具有Postdelegate to acontentmethod 定义在其state, 如示例 17-17 所示:

文件名: src/lib.rs

pub struct Post {
    state: Option<Box<dyn State>>,
    content: String,
}

impl Post {
    // --snip--
    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)
    }
    // --snip--

    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> {
        self
    }
}

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
    }
}

示例 17-17:更新contentmethod 开启Post自 delegate to acontentmethod 开启State

因为目标是将所有这些规则保留在实现State,我们调用contentmethod 对state并传递帖子 instance(即self) 作为参数。然后我们返回 使用content方法上的state价值。

我们调用as_ref方法上的Option因为我们想要对 值中Option而不是价值的所有权。因为state是一个Option<Box<dyn State>>,当我们调用as_refOption<&Box<dyn State>>返回。如果我们没有打电话as_ref,我们会收到一个错误,因为 我们不能移动state从借来的&self函数参数的 Interface。

然后,我们将unwrap方法,我们知道它永远不会 panic,因为我们 了解 上的方法Post确保state将始终包含一个Some值。这是我们讨论的案例之一 “您拥有的信息比 Compiler“部分的 知道Nonevalue 永远不可能,即使编译器无法 来理解这一点。

此时,当我们调用content&Box<dyn State>、deref 强制 将对 和&Box所以content方法将 最终在实现State特性。这意味着 我们需要添加contentStatetrait 定义,这就是 我们将根据 have,如示例 17-18 所示:

文件名: 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(),
        }
    }

    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())
        }
    }
}

trait State {
    // --snip--
    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 {
        ""
    }
}

// --snip--

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> {
        self
    }
}

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 {
    // --snip--
    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
    }
}

示例 17-18:添加content方法添加到State特性

我们为content方法返回一个空的 string 切片。这意味着我们不需要实现contentDraftPendingReview结构体。这Publishedstruct 将覆盖content方法并返回post.content.

请注意,我们需要对此方法进行生命周期注释,正如我们在 第 10 章.我们引用了post作为参数并返回 引用其中的一部分post,因此返回的引用的生命周期为 与post论点。

我们完成了 — 示例 17-11 现在都起作用了!我们已经实施了 state pattern 替换为 blog post 工作流的规则。与 rules 存在于 state 对象中,而不是分散在各个Post.

为什么不是 Enum?

您可能一直想知道为什么我们不使用enum替换为不同的 可能的 POST 状态作为变体。这当然是一个可能的解决方案,试试 它并比较最终结果,看看你更喜欢哪个!一个缺点 使用枚举是每个检查枚举值的地方都需要一个matchexpression 或类似方法处理所有可能的变体。这可以 获得比此 trait object 解决方案更重复的 get。

状态模式的权衡

我们已经证明 Rust 能够实现面向对象的状态 pattern 来封装帖子应该具有的不同类型的行为 每个状态。上面的方法Post对各种行为一无所知。这 方式,我们只需要查看一个地方就可以知道 已发布文章的不同行为方式:实现StatetraitPublished结构。

如果我们要创建一个不使用 state 的替代实现 pattern,我们可能会改用match方法 on 中的表达式Post或 即使在main检查 POST 状态并更改行为的代码 在那些地方。这意味着我们将不得不在几个地方寻找 了解帖子处于 published 状态的所有含义!这 只会增加我们添加的 state 越多:每个match表达 式 需要另一只手臂。

使用 state 模式,Post方法和我们使用的地方Post不要 需要match表达式,要添加新状态,我们只需要添加一个 new struct 并在该结构体上实现 trait 方法。

使用状态模式的实现很容易扩展以添加更多 功能性。查看维护使用状态的代码的简单性 pattern,请尝试以下一些建议:

  • 添加reject方法将帖子的状态从PendingReview返回 自Draft.
  • 需要两次调用approve之前,状态可以更改为Published.
  • 允许用户仅在帖子位于Draft州。 提示:让 state 对象负责 内容,但不负责修改Post.

状态模式的一个缺点是,因为状态实现了 状态之间的转换,一些状态是相互耦合的。如果我们 在PendingReviewPublishedScheduled, 我们必须更改PendingReview以过渡到Scheduled相反。如果PendingReview不需要 更改,但这意味着切换到 另一种设计模式。

另一个缺点是我们复制了一些逻辑。要消除一些 复制,我们可能会尝试为request_reviewapprove方法。State返回self; 但是,这会违反对象安全,因为 trait 不知道什么 混凝土self将完全正确。我们希望能够使用State作为 trait 对象,因此我们需要它的方法对对象是安全的。

其他重复包括request_reviewapprove方法Post.这两种方法都委托给 对state字段为Option并设置新的 的值state字段添加到结果中。如果我们在Post遵循此模式,我们可能会考虑定义一个宏来消除 repetition (参见第 19 章的 “Macros” 部分)。

通过完全按照为面向对象定义的状态模式来实现 语言,我们没有尽可能地充分利用 Rust 的优势。 让我们看看我们可以对blog可以制造的 crate 无效状态和转换为 Compile Time 错误。

将状态和行为编码为类型

我们将向您展示如何重新考虑状态模式以获得一组不同的 权衡取舍。而不是完全封装状态和转换,所以 外部代码不知道它们,我们会将 state 编码为不同的 类型。因此,Rust 的类型检查系统将阻止尝试使用 草稿帖子,其中仅允许通过发出编译器错误来发布帖子。

让我们考虑一下main在示例 17-11 中:

文件名: 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());
}

我们仍然允许使用 draft 状态创建新帖子Post::new以及向帖子内容添加文本的能力。但是,与其拥有content方法,我们将这样做 草稿帖子没有content方法。这样,如果我们尝试获取 草稿帖子的内容,我们将收到一个编译器错误,告诉我们方法 不存在。因此,我们不可能意外地 在生产环境中显示草稿帖子内容,因为该代码甚至无法编译。 示例 17-19 显示了Poststruct 和DraftPost结构 以及每个方法:

文件名: 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:一个Post替换为contentmethod 和DraftPost没有content方法

PostDraftPost结构体具有私有的content字段 存储博客文章文本。结构体不再具有state字段,因为 我们正在将 state 的编码移动到 struct 的类型。这Poststruct 将表示已发布的帖子,并且它有一个content方法,该 返回content.

我们仍然有一个Post::new函数,而不是返回Post,它会返回DraftPost.因为content是私有的 并且没有任何函数返回Post,则无法创建 一个Post马上。

DraftPoststruct 有一个add_text方法,因此我们可以将文本添加到content和以前一样,但请注意DraftPost没有content方法 定义!所以现在该程序确保所有帖子都以草稿帖子开头,并且 draft 帖子的内容不可供显示。任何绕过的尝试 这些约束将导致编译器错误。

将 Transition 实现为不同类型的转换

那么我们如何获得已发布的帖子呢?我们希望强制执行 draft 帖子必须先经过审查和批准,然后才能发布。一个帖子 待审核状态仍不应显示任何内容。让我们实现 这些约束通过添加另一个结构体PendingReviewPost,定义request_reviewmethod 开启DraftPost要返回PendingReviewPost和 定义一个approvemethod 开启PendingReviewPost要返回Post如 如示例 17-20 所示:

文件名: 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 {
    // --snip--
    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,
        }
    }
}

示例 17-20:一个PendingReviewPost由 叫request_reviewDraftPost以及一个approve方法将PendingReviewPost导入到已发布的Post

request_reviewapprove方法获取self因此 使用DraftPostPendingReviewPost实例和转换 它们转换为PendingReviewPost和已发布的Post分别。这边 我们不会有任何拖延DraftPost实例request_review等等。这PendingReviewPoststruct 不会 有一个content方法,因此尝试读取其内容 会导致编译器错误,就像DraftPost.因为获得 发表Post实例中,该实例具有contentmethod 定义的是调用 这approve方法在PendingReviewPost,以及获取PendingReviewPost调用request_review方法在DraftPost, 现在,我们已将博客文章工作流编码到类型系统中。

但我们也必须对main.这request_reviewapprove方法返回新实例,而不是修改它们所在的结构体 调用,因此我们需要添加更多let post =重影工作分配以保存 返回的实例。我们也不能有关于 draft 和 待审核帖子的内容是空字符串,我们也不需要它们:我们不能 编译尝试不再使用这些状态下的帖子内容的代码。 更新后的代码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使用新的 博客文章工作流的实施

我们需要做出的改变main重新分配post意味着这个 实现不再完全遵循面向对象的状态模式: 状态之间的转换不再完全封装 在Post实现。但是,我们的好处是无效状态是 现在不可能,因为类型系统和在 编译时间!这可确保某些 Bug(例如显示 未发布的帖子将在它们进入生产环境之前被发现。

尝试本节开头建议的任务。blog板条箱 在示例 17-21 之后,看看你对于这个版本的设计有什么看法 的代码。请注意,某些任务可能已经在此 设计。

我们已经看到,即使 Rust 能够实现面向对象的 设计模式、其他模式(例如将状态编码到类型系统中)、 在 Rust 中也可用。这些模式具有不同的权衡。虽然 您可能非常熟悉面向对象的模式,重新考虑 problem 来利用 Rust 的功能可以提供好处,例如 防止在编译时出现一些错误。面向对象的模式并不总是 由于 Rust 中某些功能(例如所有权)而成为 Rust 中的最佳解决方案 面向对象语言没有。

总结

无论你是否认为 Rust 是一种面向对象的语言 阅读本章,您现在知道可以使用 trait objects 来获取一些 Rust 中的面向对象功能。动态 dispatch 可以为您的代码提供一些 灵活性,以换取一点运行时性能。您可以使用这个 灵活地实现面向对象的模式,这些模式可以帮助您的代码 可维护性。Rust 还有其他功能,比如所有权, 面向对象语言没有。面向对象的模式并不总是 是利用 Rust 优势的最佳方式,但 选择。

接下来,我们将看看模式,这是 Rust 的另一个功能,它使 很大的灵活性。我们在整本书中简要地研究了它们,但是 还没有看到它们的全部功能。我们走吧!

本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准