实现面向对象的设计模式
状态模式是一种面向对象的设计模式。关键 pattern 的含义是我们定义了一个值内部可以具有的一组状态。这 状态由一组状态对象表示,值的行为 根据其状态进行更改。我们将通过一个博客示例 post 结构体,该结构体具有一个用于保存其状态的字段,该 state 对象 从设置“草稿”、“审阅”或“已发布”。
state 对象共享功能:当然,在 Rust 中,我们使用 structs 和 traits 而不是 objects 和 inheritance。每个 state 对象都负责 为了它自己的行为,并控制它何时应该变成另一个 州。保存 state 对象的值对不同的 状态的行为或何时在状态之间转换。
使用状态模式的优点是,当业务 要求更改,则不需要更改 value 来保存状态或使用该值的代码。我们只需要 更新其中一个 State 对象中的代码以更改其规则,或者 添加更多 State 对象。
首先,我们将以更传统的 面向对象的方式,那么我们将使用一种更自然的方法 锈。让我们深入研究如何使用 state 模式。
最终功能将如下所示:
- 博客文章开始时是一个空草稿。
- 草稿完成后,请求对帖子进行审核。
- 帖子获得批准后,它就会被发布。
- 只有已发布的博客文章才会返回内容进行打印,因此未经批准的文章无法返回 意外发布。
尝试对帖子进行的任何其他更改都不应产生任何影响。例如,如果我们 在我们请求审核之前尝试批准草稿博客文章,该帖子 应该保持未发布的草稿。
示例 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:演示所需
行为,我们希望blog
crate 来拥有
我们希望允许用户使用Post::new
.我们
希望允许将文本添加到博客文章中。如果我们尝试获取帖子的
内容,在批准之前,我们不应该获取任何文本,因为
Post 仍为 Draft。我们添加了assert_eq!
在用于演示的代码中
目的。一个很好的单元测试是断言草稿博客
post 从content
方法,但我们不会
为此示例编写测试。
接下来,我们想要启用对帖子的审核请求,并且我们希望content
以在等待审核时返回空字符串。当帖子
获得批准,它应该被发布,这意味着帖子的文本将
在以下情况下返回content
被调用。
请注意,我们从 crate 中交互的唯一类型是Post
类型。此类型将使用状态模式,并保存一个值为
三个状态对象之一,表示帖子可以达到的各种状态
In - 草稿、等待审核或已发布。从一种状态更改为另一种状态
将在Post
类型。状态在
响应我们库的用户在Post
实例
但他们不必直接管理 state 更改。此外,用户不能
在各州出错,比如在文章被审核之前发布它。
定义Post
和在 Draft 状态下创建新实例
让我们开始实施该库吧!我们知道我们需要一个
公共Post
struct 中,因此我们将从
结构体和关联的 public 的定义new
函数创建
实例Post
,如示例 17-12 所示。我们还会将私有的State
trait 中定义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:一个Post
struct 和new
函数创建一个新的Post
实例、State
trait 和Draft
结构
这State
trait 定义了不同 post 状态共享的行为。这
state 对象是Draft
,PendingReview
和Published
,并且它们都将
实现State
特性。目前,trait 没有任何方法,并且
我们首先只定义Draft
state 的 URL,因为那是我们
想要一个帖子开始。
当我们创建一个新的Post
,我们将其state
field 设置为Some
值
持有Box
.这Box
指向Draft
结构。
这确保了每当我们创建一个新的Post
,它将以
草稿。因为state
字段为Post
是私有的,没有办法
创建一个Post
在任何其他州!在Post::new
函数,我们将content
field 转换为新的空String
.
存储文章内容的文本
我们在示例 17-11 中看到,我们希望能够调用一个名为add_text
并传递一个&str
,然后添加为
博客文章。我们将其作为方法实现,而不是公开content
field 设置为pub
,以便稍后我们可以实现一个方法,该方法将控制
这content
读取 field 的数据。这add_text
method 很漂亮
简单明了,那么让我们将示例 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_text
method 接受对self
,因为我们是
更改Post
实例add_text
上。然后我们调用push_str
在String
在content
并传递text
要添加到 的参数
已保存的content
.此行为不取决于帖子所处的状态,
所以它不是 state 模式的一部分。这add_text
方法不交互
使用state
field 中,但它是我们想要的行为的一部分
支持。
确保草稿帖子的内容为空
即使在我们调用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:为
这content
method 开启Post
这总是返回一个空的字符串 slice
添加此content
方法,示例 17-11 中的所有内容到第 7 行
按预期工作。
请求对帖子的审核会更改其状态
接下来,我们需要添加请求对帖子进行审核的功能,这应该
将其状态从Draft
自PendingReview
.示例 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
方法Post
和State
特性
我们给予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_review
method 需要获得所有权
的状态值。这就是Option
在state
字段为Post
进来:我们调用take
方法获取Some
值从state
字段并留下一个None
取而代之的是 Rust 不允许我们拥有
结构中未填充的字段。这样我们就可以将state
值 out ofPost
而不是借用它。然后我们将设置帖子的state
值设置为
此作的结果。
我们需要设置state
自None
临时设置,而不是直接设置
使用类似self.state = self.state.request_review();
获取
这state
价值。这确保了Post
不能使用旧的state
值
我们已将其转变为新状态。
这request_review
method 开启Draft
返回一个新的PendingReview
struct 的 API API 中,它表示 Post 等待
回顾。这PendingReview
struct 还实现了request_review
方法
但不执行任何转换。相反,它会返回自身,因为当我们
请求对PendingReview
state 中,它应该保持
在PendingReview
州。
现在我们可以开始看到 state 模式的优势:request_review
method 开启Post
无论其state
价值。每
state 对自己的规则负责。
我们将保留content
method 开启Post
按原样返回一个空字符串
片。我们现在可以有一个Post
在PendingReview
state 以及Draft
state 中,但我们希望在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:实现approve
method 开启Post
和State
特性
我们将approve
方法添加到State
trait 并添加一个新的结构体,该
实现State
这Published
州。
类似于request_review
上PendingReview
有效,如果我们调用approve
方法在Draft
,它不会有任何效果,因为approve
将
返回self
.当我们调用approve
上PendingReview
,它会返回一个新的
boxed 实例的Published
结构。这Published
struct 实现State
trait 的request_review
方法和approve
方法,它会返回自身,因为 post 应该保留在Published
在这些情况下。
现在我们需要更新content
method 开启Post
.我们想要价值
返回自content
依赖于Post
,所以我们是
将具有Post
delegate to acontent
method 定义在其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:更新content
method 开启Post
自
delegate to acontent
method 开启State
因为目标是将所有这些规则保留在实现State
,我们调用content
method 对state
并传递帖子
instance(即self
) 作为参数。然后我们返回
使用content
方法上的state
价值。
我们调用as_ref
方法上的Option
因为我们想要对
值中Option
而不是价值的所有权。因为state
是一个Option<Box<dyn State>>
,当我们调用as_ref
一Option<&Box<dyn State>>
返回。如果我们没有打电话as_ref
,我们会收到一个错误,因为
我们不能移动state
从借来的&self
函数参数的 Interface。
然后,我们将unwrap
方法,我们知道它永远不会 panic,因为我们
了解 上的方法Post
确保state
将始终包含一个Some
值。这是我们讨论的案例之一
“您拥有的信息比
Compiler“部分的
知道None
value 永远不可能,即使编译器无法
来理解这一点。
此时,当我们调用content
在&Box<dyn State>
、deref 强制
将对 和&
Box
所以content
方法将
最终在实现State
特性。这意味着
我们需要添加content
到State
trait 定义,这就是
我们将根据
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 切片。这意味着我们不需要实现content
在Draft
和PendingReview
结构体。这Published
struct 将覆盖content
方法并返回post.content
.
请注意,我们需要对此方法进行生命周期注释,正如我们在
第 10 章.我们引用了post
作为参数并返回
引用其中的一部分post
,因此返回的引用的生命周期为
与post
论点。
我们完成了 — 示例 17-11 现在都起作用了!我们已经实施了 state
pattern 替换为 blog post 工作流的规则。与
rules 存在于 state 对象中,而不是分散在各个Post
.
为什么不是 Enum?
您可能一直想知道为什么我们不使用enum
替换为不同的
可能的 POST 状态作为变体。这当然是一个可能的解决方案,试试
它并比较最终结果,看看你更喜欢哪个!一个缺点
使用枚举是每个检查枚举值的地方都需要一个match
expression 或类似方法处理所有可能的变体。这可以
获得比此 trait object 解决方案更重复的 get。
状态模式的权衡
我们已经证明 Rust 能够实现面向对象的状态
pattern 来封装帖子应该具有的不同类型的行为
每个状态。上面的方法Post
对各种行为一无所知。这
方式,我们只需要查看一个地方就可以知道
已发布文章的不同行为方式:实现State
traitPublished
结构。
如果我们要创建一个不使用 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
.
状态模式的一个缺点是,因为状态实现了
状态之间的转换,一些状态是相互耦合的。如果我们
在PendingReview
和Published
如Scheduled
,
我们必须更改PendingReview
以过渡到Scheduled
相反。如果PendingReview
不需要
更改,但这意味着切换到
另一种设计模式。
另一个缺点是我们复制了一些逻辑。要消除一些
复制,我们可能会尝试为request_review
和approve
方法。State
返回self
;
但是,这会违反对象安全,因为 trait 不知道什么
混凝土self
将完全正确。我们希望能够使用State
作为
trait 对象,因此我们需要它的方法对对象是安全的。
其他重复包括request_review
和approve
方法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 显示了Post
struct 和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
替换为content
method 和DraftPost
没有content
方法
这Post
和DraftPost
结构体具有私有的content
字段
存储博客文章文本。结构体不再具有state
字段,因为
我们正在将 state 的编码移动到 struct 的类型。这Post
struct 将表示已发布的帖子,并且它有一个content
方法,该
返回content
.
我们仍然有一个Post::new
函数,而不是返回Post
,它会返回DraftPost
.因为content
是私有的
并且没有任何函数返回Post
,则无法创建
一个Post
马上。
这DraftPost
struct 有一个add_text
方法,因此我们可以将文本添加到content
和以前一样,但请注意DraftPost
没有content
方法
定义!所以现在该程序确保所有帖子都以草稿帖子开头,并且 draft
帖子的内容不可供显示。任何绕过的尝试
这些约束将导致编译器错误。
将 Transition 实现为不同类型的转换
那么我们如何获得已发布的帖子呢?我们希望强制执行 draft
帖子必须先经过审查和批准,然后才能发布。一个帖子
待审核状态仍不应显示任何内容。让我们实现
这些约束通过添加另一个结构体PendingReviewPost
,定义request_review
method 开启DraftPost
要返回PendingReviewPost
和
定义一个approve
method 开启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_review
上DraftPost
以及一个approve
方法将PendingReviewPost
导入到已发布的Post
这request_review
和approve
方法获取self
因此
使用DraftPost
和PendingReviewPost
实例和转换
它们转换为PendingReviewPost
和已发布的Post
分别。这边
我们不会有任何拖延DraftPost
实例request_review
等等。这PendingReviewPost
struct 不会
有一个content
方法,因此尝试读取其内容
会导致编译器错误,就像DraftPost
.因为获得
发表Post
实例中,该实例具有content
method 定义的是调用
这approve
方法在PendingReviewPost
,以及获取PendingReviewPost
调用request_review
方法在DraftPost
,
现在,我们已将博客文章工作流编码到类型系统中。
但我们也必须对main
.这request_review
和approve
方法返回新实例,而不是修改它们所在的结构体
调用,因此我们需要添加更多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/)为准