mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-14 04:41:49 +08:00
update to ch17-03
This commit is contained in:
parent
6493652a3b
commit
5b156454d8
@ -2,13 +2,13 @@
|
||||
|
||||
> [ch17-01-what-is-oo.md](https://github.com/rust-lang/book/blob/main/src/ch17-01-what-is-oo.md)
|
||||
> <br>
|
||||
> commit 34caca254c3e08ff9fe3ad985007f45e92577c03
|
||||
> commit 3fa4eeca3a57f257e3569055d808b6a76e9b70ee
|
||||
|
||||
关于一个语言被称为面向对象所需的功能,在编程社区内并未达成一致意见。Rust 被很多不同的编程范式影响,包括面向对象编程;比如第十三章提到了来自函数式编程的特性。面向对象编程语言所共享的一些特性往往是对象、封装和继承。让我们看一下这每一个概念的含义以及 Rust 是否支持他们。
|
||||
|
||||
### 对象包含数据和行为
|
||||
|
||||
由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides(Addison-Wesley Professional, 1994)编写的书 *Design Patterns: Elements of Reusable Object-Oriented Software* 被俗称为 *The Gang of Four* (字面意思为“四人帮”),它是面向对象编程模式的目录。它这样定义面向对象编程:
|
||||
由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides(Addison-Wesley Professional, 1994)编写的书 *Design Patterns: Elements of Reusable Object-Oriented Software* 被俗称为 *The Gang of Four* (字面意思为 “四人帮”),它是面向对象编程模式的目录。它这样定义面向对象编程:
|
||||
|
||||
> Object-oriented programs are made up of objects. An *object* packages both
|
||||
> data and the procedures that operate on that data. The procedures are
|
||||
@ -26,11 +26,8 @@
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
pub struct AveragedCollection {
|
||||
list: Vec<i32>,
|
||||
average: f64,
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-01/src/lib.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-1: `AveragedCollection` 结构体维护了一个整型列表和集合中所有元素的平均值。</span>
|
||||
@ -39,37 +36,8 @@ pub struct AveragedCollection {
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub struct AveragedCollection {
|
||||
# list: Vec<i32>,
|
||||
# average: f64,
|
||||
# }
|
||||
impl AveragedCollection {
|
||||
pub fn add(&mut self, value: i32) {
|
||||
self.list.push(value);
|
||||
self.update_average();
|
||||
}
|
||||
|
||||
pub fn remove(&mut self) -> Option<i32> {
|
||||
let result = self.list.pop();
|
||||
match result {
|
||||
Some(value) => {
|
||||
self.update_average();
|
||||
Some(value)
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn average(&self) -> f64 {
|
||||
self.average
|
||||
}
|
||||
|
||||
fn update_average(&mut self) {
|
||||
let total: i32 = self.list.iter().sum();
|
||||
self.average = total as f64 / self.list.len() as f64;
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-02/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-2: 在`AveragedCollection` 结构体上实现了`add`、`remove` 和 `average` 公有方法</span>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch17-02-trait-objects.md](https://github.com/rust-lang/book/blob/main/src/ch17-02-trait-objects.md)
|
||||
> <br>
|
||||
> commit 7b23a000fc511d985069601eb5b09c6017e609eb
|
||||
> commit 727ef100a569d9aa0b9da3a498a346917fadc979
|
||||
|
||||
在第八章中,我们谈到了 vector 只能存储同种类型元素的局限。示例 8-10 中提供了一个定义 `SpreadsheetCell` 枚举来储存整型,浮点型和文本成员的替代方案。这意味着可以在每个单元中储存不同类型的数据,并仍能拥有一个代表一排单元的 vector。这在当编译代码时就知道希望可以交替使用的类型为固定集合的情况下是完全可行的。
|
||||
|
||||
@ -22,10 +22,8 @@
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
pub trait Draw {
|
||||
fn draw(&self);
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-03/src/lib.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-3:`Draw` trait 的定义</span>
|
||||
@ -34,14 +32,8 @@ pub trait Draw {
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub trait Draw {
|
||||
# fn draw(&self);
|
||||
# }
|
||||
#
|
||||
pub struct Screen {
|
||||
pub components: Vec<Box<dyn Draw>>,
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-04/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-4: 一个 `Screen` 结构体的定义,它带有一个字段 `components`,其包含实现了 `Draw` trait 的 trait 对象的 vector</span>
|
||||
@ -50,22 +42,8 @@ pub struct Screen {
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub trait Draw {
|
||||
# fn draw(&self);
|
||||
# }
|
||||
#
|
||||
# pub struct Screen {
|
||||
# pub components: Vec<Box<dyn Draw>>,
|
||||
# }
|
||||
#
|
||||
impl Screen {
|
||||
pub fn run(&self) {
|
||||
for component in self.components.iter() {
|
||||
component.draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-05/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-5:在 `Screen` 上实现一个 `run` 方法,该方法在每个 component 上调用 `draw` 方法</span>
|
||||
@ -74,23 +52,8 @@ impl Screen {
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub trait Draw {
|
||||
# fn draw(&self);
|
||||
# }
|
||||
#
|
||||
pub struct Screen<T: Draw> {
|
||||
pub components: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T> Screen<T>
|
||||
where T: Draw {
|
||||
pub fn run(&self) {
|
||||
for component in self.components.iter() {
|
||||
component.draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-06/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-6: 一种 `Screen` 结构体的替代实现,其 `run` 方法使用泛型和 trait bound</span>
|
||||
@ -105,22 +68,8 @@ impl<T> Screen<T>
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub trait Draw {
|
||||
# fn draw(&self);
|
||||
# }
|
||||
#
|
||||
pub struct Button {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
impl Draw for Button {
|
||||
fn draw(&self) {
|
||||
// 实际绘制按钮的代码
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-07/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-7: 一个实现了 `Draw` trait 的 `Button` 结构体</span>
|
||||
@ -132,19 +81,7 @@ impl Draw for Button {
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
use gui::Draw;
|
||||
|
||||
struct SelectBox {
|
||||
width: u32,
|
||||
height: u32,
|
||||
options: Vec<String>,
|
||||
}
|
||||
|
||||
impl Draw for SelectBox {
|
||||
fn draw(&self) {
|
||||
// code to actually draw a select box
|
||||
}
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-08/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-8: 另一个使用 `gui` 的 crate 中,在 `SelectBox` 结构体上实现 `Draw` trait</span>
|
||||
@ -154,30 +91,7 @@ impl Draw for SelectBox {
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
use gui::{Screen, Button};
|
||||
|
||||
fn main() {
|
||||
let screen = Screen {
|
||||
components: vec![
|
||||
Box::new(SelectBox {
|
||||
width: 75,
|
||||
height: 10,
|
||||
options: vec![
|
||||
String::from("Yes"),
|
||||
String::from("Maybe"),
|
||||
String::from("No")
|
||||
],
|
||||
}),
|
||||
Box::new(Button {
|
||||
width: 50,
|
||||
height: 10,
|
||||
label: String::from("OK"),
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
screen.run();
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-09/src/main.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-9: 使用 trait 对象来存储实现了相同 trait 的不同类型的值</span>
|
||||
@ -193,32 +107,15 @@ fn main() {
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
use gui::Screen;
|
||||
|
||||
fn main() {
|
||||
let screen = Screen {
|
||||
components: vec![
|
||||
Box::new(String::from("Hi")),
|
||||
],
|
||||
};
|
||||
|
||||
screen.run();
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-10/src/main.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-10: 尝试使用一种没有实现 trait 对象的 trait 的类型</span>
|
||||
|
||||
我们会遇到这个错误,因为 `String` 没有实现 `rust_gui::Draw` trait:
|
||||
|
||||
```text
|
||||
error[E0277]: the trait bound `std::string::String: gui::Draw` is not satisfied
|
||||
--> src/main.rs:7:13
|
||||
|
|
||||
7 | Box::new(String::from("Hi")),
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait gui::Draw is not
|
||||
implemented for `std::string::String`
|
||||
|
|
||||
= note: required for the cast to the object type `gui::Draw`
|
||||
```console
|
||||
{{#include ../listings/ch17-oop/listing-17-10/output.txt}}
|
||||
```
|
||||
|
||||
这告诉了我们,要么是我们传递了并不希望传递给 `Screen` 的类型并应该提供其他类型,要么应该在 `String` 上实现 `Draw` 以便 `Screen` 可以调用其上的 `draw`。
|
||||
@ -229,50 +126,6 @@ error[E0277]: the trait bound `std::string::String: gui::Draw` is not satisfied
|
||||
|
||||
当使用 trait 对象时,Rust 必须使用动态分发。编译器无法知晓所有可能用于 trait 对象代码的类型,所以它也不知道应该调用哪个类型的哪个方法实现。为此,Rust 在运行时使用 trait 对象中的指针来知晓需要调用哪个方法。动态分发也阻止编译器有选择的内联方法代码,这会相应的禁用一些优化。尽管在编写示例 17-5 和可以支持示例 17-9 中的代码的过程中确实获得了额外的灵活性,但仍然需要权衡取舍。
|
||||
|
||||
### Trait 对象要求对象安全
|
||||
|
||||
只有 **对象安全**(*object safe*)的 trait 才可以组成 trait 对象。围绕所有使得 trait 对象安全的属性存在一些复杂的规则,不过在实践中,只涉及到两条规则。如果一个 trait 中所有的方法有如下属性时,则该 trait 是对象安全的:
|
||||
|
||||
- 返回值类型不为 `Self`
|
||||
- 方法没有任何泛型类型参数
|
||||
|
||||
`Self` 关键字是我们要实现 trait 或方法的类型的别名。对象安全对于 trait 对象是必须的,因为一旦有了 trait 对象,就不再知晓实现该 trait 的具体类型是什么了。如果 trait 方法返回具体的 `Self` 类型,但是 trait 对象忘记了其真正的类型,那么方法不可能使用已经忘却的原始具体类型。同理对于泛型类型参数来说,当使用 trait 时其会放入具体的类型参数:此具体类型变成了实现该 trait 的类型的一部分。当使用 trait 对象时其具体类型被抹去了,故无从得知放入泛型参数类型的类型是什么。
|
||||
|
||||
一个 trait 的方法不是对象安全的例子是标准库中的 `Clone` trait。`Clone` trait 的 `clone` 方法的参数签名看起来像这样:
|
||||
|
||||
```rust
|
||||
pub trait Clone {
|
||||
fn clone(&self) -> Self;
|
||||
}
|
||||
```
|
||||
|
||||
`String` 实现了 `Clone` trait,当在 `String` 实例上调用 `clone` 方法时会得到一个 `String` 实例。类似的,当调用 `Vec<T>` 实例的 `clone` 方法会得到一个 `Vec<T>` 实例。`clone` 的签名需要知道什么类型会代替 `Self`,因为这是它的返回值。
|
||||
|
||||
如果尝试做一些违反有关 trait 对象的对象安全规则的事情,编译器会提示你。例如,如果尝试实现示例 17-4 中的 `Screen` 结构体来存放实现了 `Clone` trait 而不是 `Draw` trait 的类型,像这样:
|
||||
|
||||
```rust,ignore,does_not_compile
|
||||
pub struct Screen {
|
||||
pub components: Vec<Box<dyn Clone>>,
|
||||
}
|
||||
```
|
||||
|
||||
将会得到如下错误:
|
||||
|
||||
```text
|
||||
error[E0038]: the trait `std::clone::Clone` cannot be made into an object
|
||||
--> src/lib.rs:2:5
|
||||
|
|
||||
2 | pub components: Vec<Box<dyn Clone>>,
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone`
|
||||
cannot be made into an object
|
||||
|
|
||||
= note: the trait cannot require that `Self : Sized`
|
||||
```
|
||||
|
||||
这意味着不能以这种方式使用此 trait 作为 trait 对象。如果你对对象安全的更多细节感兴趣,请查看 [Rust RFC 255]。
|
||||
|
||||
[Rust RFC 255]: https://github.com/rust-lang/rfcs/blob/master/text/0255-object-safety.md
|
||||
|
||||
[performance-of-code-using-generics]:
|
||||
ch10-01-syntax.html#performance-of-code-using-generics
|
||||
[dynamically-sized]: ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait
|
||||
ch10-01-syntax.html#泛型代码的性能
|
||||
[dynamically-sized]: ch19-04-advanced-types.html#动态大小类型和-sized-trait
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch17-03-oo-design-patterns.md](https://github.com/rust-lang/book/blob/main/src/ch17-03-oo-design-patterns.md)
|
||||
> <br>
|
||||
> commit 7e219336581c41a80fd41f4fbe615fecb6ed0a7d
|
||||
> commit 851449061b74d8b15adca936350a3fca6160ff39
|
||||
|
||||
**状态模式**(*state pattern*)是一个面向对象设计模式。该模式的关键在于一个值有某些内部状态,体现为一系列的 **状态对象**,同时值的行为随着其内部状态而改变。状态对象共享功能:当然,在 Rust 中使用结构体和 trait 而不是对象和继承。每一个状态对象负责其自身的行为,以及该状态何时应当转移至另一个状态。持有一个状态对象的值对于不同状态的行为以及何时状态转移毫不知情。
|
||||
|
||||
@ -21,26 +21,13 @@
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
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());
|
||||
}
|
||||
```rust,ignore,does_not_compile
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-11/src/main.rs:all}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-11: 展示了 `blog` crate 期望行为的代码</span>
|
||||
|
||||
我们希望允许用户使用 `Post::new` 创建一个新的博文草案。接着希望能在草案阶段为博文编写一些文本。如果尝试在审核之前立即打印出博文的内容,什么也不会发生因为博文仍然是草案。这里增加的 `assert_eq!` 出于演示目的。一个好的单元测试将是断言草案博文的 `content` 方法返回空字符串,不过我们并不准备为这个例子编写单元测试。
|
||||
我们希望允许用户使用 `Post::new` 创建一个新的博文草案。也希望能在草案阶段为博文编写一些文本。如果在审批之前尝试立刻获取博文的内容,不应该获取到任何文本因为博文仍然是草案。一个好的单元测试将是断言草案博文的 `content` 方法返回空字符串,不过我们并不准备为这个例子编写单元测试。
|
||||
|
||||
接下来,我们希望能够请求审核博文,而在等待审核的阶段 `content` 应该仍然返回空字符串。最后当博文审核通过,它应该被发表,这意味着当调用 `content` 时博文的文本将被返回。
|
||||
|
||||
@ -52,29 +39,10 @@ fn main() {
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
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 {}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-12/src/lib.rs}}
|
||||
```
|
||||
|
||||
|
||||
<span class="caption">示例 17-12: `Post` 结构体的定义和新建 `Post` 实例的 `new` 函数,`State` trait 和结构体 `Draft`</span>
|
||||
|
||||
`State` trait 定义了所有不同状态的博文所共享的行为,同时 `Draft`、`PendingReview` 和 `Published` 状态都会实现 `State` 状态。现在这个 trait 并没有任何方法,同时开始将只定义 `Draft` 状态因为这是我们希望博文的初始状态。
|
||||
@ -87,17 +55,8 @@ impl State for Draft {}
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub struct Post {
|
||||
# content: String,
|
||||
# }
|
||||
#
|
||||
impl Post {
|
||||
// --snip--
|
||||
pub fn add_text(&mut self, text: &str) {
|
||||
self.content.push_str(text);
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-13/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-13: 实现方法 `add_text` 来向博文的 `content` 增加文本</span>
|
||||
@ -110,17 +69,8 @@ impl Post {
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub struct Post {
|
||||
# content: String,
|
||||
# }
|
||||
#
|
||||
impl Post {
|
||||
// --snip--
|
||||
pub fn content(&self) -> &str {
|
||||
""
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-14/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">列表 17-14: 增加一个 `Post` 的 `content` 方法的占位实现,它总是返回一个空字符串 slice</span>
|
||||
@ -133,40 +83,8 @@ impl Post {
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub struct Post {
|
||||
# state: Option<Box<dyn State>>,
|
||||
# content: String,
|
||||
# }
|
||||
#
|
||||
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
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-15/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-15: 实现 `Post` 和 `State` trait 的 `request_review` 方法</span>
|
||||
@ -191,98 +109,29 @@ impl State for PendingReview {
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub struct Post {
|
||||
# state: Option<Box<dyn State>>,
|
||||
# content: String,
|
||||
# }
|
||||
#
|
||||
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 {
|
||||
# fn request_review(self: Box<Self>) -> Box<dyn State> {
|
||||
# Box::new(PendingReview {})
|
||||
# }
|
||||
#
|
||||
// --snip--
|
||||
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
|
||||
# }
|
||||
#
|
||||
// --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
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-16/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-16: 为 `Post` 和 `State` trait 实现 `approve` 方法</span>
|
||||
|
||||
这里为 `State` trait 增加了 `approve` 方法,并新增了一个实现了 `State` 的结构体,`Published` 状态。
|
||||
|
||||
类似于 `request_review`,如果对 `Draft` 调用 `approve` 方法,并没有任何效果,因为它会返回 `self`。当对 `PendingReview` 调用 `approve` 时,它返回一个新的、装箱的 `Published` 结构体的实例。`Published` 结构体实现了 `State` trait,同时对于 `request_review` 和 `approve` 两方法来说,它返回自身,因为在这两种情况博文应该保持 `Published` 状态。
|
||||
类似于 `PendingReview` 中 `request_review` 的工作方式,如果对 `Draft` 调用 `approve` 方法,并没有任何效果,因为它会返回 `self`。当对 `PendingReview` 调用 `approve` 时,它返回一个新的、装箱的 `Published` 结构体的实例。`Published` 结构体实现了 `State` trait,同时对于 `request_review` 和 `approve` 两方法来说,它返回自身,因为在这两种情况博文应该保持 `Published` 状态。
|
||||
|
||||
现在更新 `Post` 的 `content` 方法:如果状态为 `Published` 希望返回博文 `content` 字段的值;否则希望返回空字符串 slice,如示例 17-17 所示:
|
||||
现在需要更新 `Post` 的 `content` 方法。我们希望 `content` 根据 `Post` 的当前状态返回值,所以需要 `Post` 代理一个定义于 `state` 上的 `content` 方法,如实例 17-17 所示:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# trait State {
|
||||
# fn content<'a>(&self, post: &'a Post) -> &'a str;
|
||||
# }
|
||||
# pub struct Post {
|
||||
# state: Option<Box<dyn State>>,
|
||||
# content: String,
|
||||
# }
|
||||
#
|
||||
impl Post {
|
||||
// --snip--
|
||||
pub fn content(&self) -> &str {
|
||||
self.state.as_ref().unwrap().content(self)
|
||||
}
|
||||
// --snip--
|
||||
}
|
||||
```rust,ignore,does_not_compile
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-17/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-17: 更新 `Post` 的 `content` 方法来委托调用 `State` 的`content` 方法</span>
|
||||
|
||||
因为目标是将所有像这样的规则保持在实现了 `State` 的结构体中,我们将调用 `state` 中的值的 `content` 方法并传递博文实例(也就是 `self`)作为参数。接着返回 `state` 值的 `content` 方法的返回值。
|
||||
|
||||
这里调用 `Option` 的 `as_ref` 方法是因为需要 `Option` 中值的引用而不是获取其所有权。因为 `state` 是一个 `Option<Box<State>>`,调用 `as_ref` 会返回一个 `Option<&Box<State>>`。如果不调用 `as_ref`,将会得到一个错误,因为不能将 `state` 移动出借用的 `&self` 函数参数。
|
||||
这里调用 `Option` 的 `as_ref` 方法是因为需要 `Option` 中值的引用而不是获取其所有权。因为 `state` 是一个 `Option<&Box<dynState>>`,调用 `as_ref` 会返回一个 `Option<&Box<State>>`。如果不调用 `as_ref`,将会得到一个错误,因为不能将 `state` 移动出借用的 `&self` 函数参数。
|
||||
|
||||
接着调用 `unwrap` 方法,这里我们知道它永远也不会 panic,因为 `Post` 的所有方法都确保在他们返回时 `state` 会有一个 `Some` 值。这就是一个第十二章 [“当我们比编译器知道更多的情况”][more-info-than-rustc] 部分讨论过的我们知道 `None` 是不可能的而编译器却不能理解的情况。
|
||||
|
||||
@ -290,26 +139,8 @@ impl Post {
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub struct Post {
|
||||
# content: String
|
||||
# }
|
||||
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
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-18/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-18: 为 `State` trait 增加 `content` 方法</span>
|
||||
@ -351,46 +182,15 @@ impl State for Published {
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
# use blog::Post;
|
||||
|
||||
fn main() {
|
||||
let mut post = Post::new();
|
||||
|
||||
post.add_text("I ate a salad for lunch today");
|
||||
assert_eq!("", post.content());
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-11/src/main.rs:here}}
|
||||
```
|
||||
|
||||
我们仍然希望能够使用 `Post::new` 创建一个新的草案博文,并能够增加博文的内容。不过不同于存在一个草案博文时返回空字符串的 `content` 方法,我们将使草案博文完全没有 `content` 方法。这样如果尝试获取草案博文的内容,将会得到一个方法不存在的编译错误。这使得我们不可能在生产环境意外显示出草案博文的内容,因为这样的代码甚至就不能编译。示例 17-19 展示了 `Post` 结构体、`DraftPost` 结构体以及各自的方法的定义:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
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);
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-19/src/lib.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-19: 带有 `content` 方法的 `Post` 和没有 `content` 方法的 `DraftPost`</span>
|
||||
@ -407,36 +207,8 @@ impl DraftPost {
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
# pub struct Post {
|
||||
# content: String,
|
||||
# }
|
||||
#
|
||||
# pub struct DraftPost {
|
||||
# content: String,
|
||||
# }
|
||||
#
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
```rust,noplayground
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-20/src/lib.rs:here}}
|
||||
```
|
||||
|
||||
<span class="caption">列表 17-20: `PendingReviewPost` 通过调用 `DraftPost` 的 `request_review` 创建,`approve` 方法将 `PendingReviewPost` 变为发布的 `Post`</span>
|
||||
@ -448,19 +220,7 @@ impl PendingReviewPost {
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
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());
|
||||
}
|
||||
{{#rustdoc_include ../listings/ch17-oop/listing-17-21/src/main.rs}}
|
||||
```
|
||||
|
||||
<span class="caption">示例 17-21: `main` 中使用新的博文工作流实现的修改</span>
|
||||
@ -477,5 +237,5 @@ fn main() {
|
||||
|
||||
接下来,让我们看看另一个提供了多样灵活性的 Rust 功能:模式。贯穿全书的模式, 我们已经和它们打过照面了,但并没有见识过它们的全部本领。让我们开始探索吧!
|
||||
|
||||
[more-info-than-rustc]: ch09-03-to-panic-or-not-to-panic.html#cases-in-which-you-have-more-information-than-the-compiler
|
||||
[macros]: ch19-06-macros.html#macros
|
||||
[more-info-than-rustc]: ch09-03-to-panic-or-not-to-panic.html#当我们比编译器知道更多的情况
|
||||
[macros]: ch19-06-macros.html#宏
|
||||
|
Loading…
Reference in New Issue
Block a user