diff --git a/src/ch17-02-trait-objects.md b/src/ch17-02-trait-objects.md index fc09d6a..78d1385 100644 --- a/src/ch17-02-trait-objects.md +++ b/src/ch17-02-trait-objects.md @@ -2,28 +2,27 @@ > [ch17-02-trait-objects.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch17-02-trait-objects.md) >
-> commit 67876e3ef5323ce9d394f3ea6b08cb3d173d9ba9 +> commit ccdd9ca7aacea4cefeb6a96e7ffb9ea91a923abd - 在第八章中,我们谈到了 vector 只能存储同种类型元素的局限。在列表 8-1 中有一个例子,其中定义了一个拥有分别存放整型、浮点型和文本型成员的枚举类型 `SpreadsheetCell`,使用这个枚举的 vector 可以在每一个单元格(cell)中储存不同类型的数据,并使得 vector 整体仍然代表一行(row)单元格。这当编译代码时就知道希望可以交替使用的类型为固定集合的情况下是可行的。 + 在第八章中,我们谈到了 vector 只能存储同种类型元素的局限。示例 8-10 中提供了一个定义 `SpreadsheetCell` 枚举来储存整型,浮点型和文本成员的替代方案。这意味着可以在每个单元中储存不同类型的数据,并仍能拥有一个代表一排单元的 vector。这在当编译代码时就知道希望可以交替使用的类型为固定集合的情况下是完全可行的。 + +然而有时,我们希望库用户在特定情况下能够扩展有效的类型集合。为了展示如何实现这一点,这里将创建一个图形用户接口(Graphical User Interface, GUI)工具的例子,其它通过遍历列表并调用每一个项目的 `draw` 方法来将其绘制到屏幕上;此乃一个 GUI 工具的常见技术。我们将要创建一个叫做 `rust_gui` 的库 crate,它含一个 GUI 库的结构。这个 GUI 库包含一些可供开发者使用的类型,比如 `Button` 或 `TextField`。在此之上,`rust_gui` 的用户希望创建自定义的可以绘制于屏幕上的类型:比如,一个程序员可能会增加 `Image`,另一个可能会增加 `SelectBox`。 - +这个例子中并不会实现一个功能完善的 GUI 库,不过会展示其中各个部分是如何结合在一起的。编写库的时候,我们不可能知晓并定义所有其他程序员希望创建的类型。我们所知晓的是 `rust_gui` 需要记录一系列不同类型的值,并需要能够对其中每一个值调用 `draw` 方法。这里无需知道调用 `draw` 方法时具体会发生什么,只需提供可供这些值调用的方法即可。 -有时,我们希望使用的类型的集合对于使用库的程序员来说是可扩展的。例如,很多图形用户接口(GUI)工具有一个项目列表的概念,它通过遍历列表并调用每一个项目的 `draw` 方法来将其绘制到屏幕上。我们将要创建一个叫做 `rust_gui` 的库 crate,它含一个 GUI 库的结构。这个 GUI 库包含一些可供开发者使用的类型,比如 `Button` 或 `TextField`。使用 `rust_gui` 的程序员会想要创建更多可以绘制在屏幕上的类型:其中一些可能会增加一个 `Image`,而另一些可能会增加一个 `SelectBox`。本章节并不准备实现一个功能完善的 GUI 库,不过会展示其中各个部分是如何结合在一起的。 - -编写 `rust_gui` 库时,我们并不知道其他程序员想要创建的全部类型,所以无法定义一个 `enum` 来包含所有这些类型。我们所要做的是使 `rust_gui` 能够记录一系列不同类型的值,并能够对其中每一个值调用 `draw` 方法。 GUI 库不需要知道当调用 `draw` 方法时具体会发生什么,只需提供这些值可供调用的方法即可。 - -在拥有继承的语言中,我们可能定义一个名为 `Component` 的类,该类上有一个 `draw` 方法。其他的类比如 `Button`、`Image` 和 `SelectBox` 会从 `Component` 派生并因此继承 `draw` 方法。它们各自都可以覆盖 `draw` 方法来定义自己的行为,但是框架会把所有这些类型当作是 `Component` 的实例,并在其上调用 `draw`。 +在拥有继承的语言中,可以定义一个名为 `Component` 的类,该类上有一个 `draw` 方法。其他的类比如 `Button`、`Image` 和 `SelectBox` 会从 `Component` 派生并因此继承 `draw` 方法。它们各自都可以覆盖 `draw` 方法来定义自己的行为,但是框架会把所有这些类型当作是 `Component` 的实例,并在其上调用 `draw`。不过 Rust 并没有继承,我们得另寻出路。 ### 定义通用行为的 trait -不过,在 Rust 中,我们可以定义一个 `Draw` trait,包含名为 `draw` 的方法。接着可以定义一个存放**trait 对象**(*trait -object*)的 vector,trait 对象是一个位于某些指针,比如 `&` 引用或 `Box` 智能指针,之后的 trait。第十九章会讲到为何 trait 对象必须位于指针之后的原因。 +为了实现 `rust_gui` 所期望拥有的行为,定义一个 `Draw` trait,其包含名为 `draw` 的方法。接着可以定义一个存放 **trait 对象**(*trait object*) 的 vector。trait 对象指向一个实现了我们指定 trait 的类型实例。我们通过指定某些指针,比如 `&` 引用或 `Box` 智能指针,接着指定相关的 trait(第十九章动态大小类型部分会介绍 trait 对象必须使用指针的原因)。我们可以使用 trait 对象代替泛型或具体类型。任何使用 trait 对象的位置,Rust 的类型系统会在编译时确保任何在此上下文中使用的值会实现其 trait 对象的 trait。如此便无需在编译时就知晓所有可能的类型。 -之前提到过,我们并不将结构体与枚举称之为“对象”,以便与其他语言中的对象相区别。结构体与枚举和 `impl` 块中的行为是分开的,不同于其他语言中将数据和行为组合进一个称为对象的概念中。trait 对象将由指向具体对象的指针构成的数据和定义于 trait 中方法的行为结合在一起,从这种意义上说它**则**更类似其他语言中的对象。不过 trait 对象与其他语言中的对象是不同的,因为不能向 trait 对象增加数据。trait 对象并不像其他语言中的对象那么通用:他们(trait 对象)的作用是允许对通用行为的抽象。 + + -trait 对象定义了在给定情况下所需的行为。接着就可以在要使用具体类型或泛型的地方使用 trait 来作为 trait 对象。Rust 的类型系统会确保任何我们替换为 trait 对象的值都会实现了 trait 的方法。这样就无需在编译时就知道所有可能的类型,就能够用同样的方法处理所有的实例。列表 17-3 展示了如何定义一个带有 `draw` 方法的 trait `Draw`: +之前提到过,Rust 刻意不将结构体与枚举称为 “对象”,以便与其他语言中的对象相区别。在结构体或枚举中,结构体字段中的数据和 `impl` 块中的行为是分开的,不同于其他语言中将数据和行为组合进一个称为对象的概念中。trait 对象将数据和行为两者相结合,从这种意义上说 **则** 其更类似其他语言中的对象。不过 trait 对象不同于传统的对象,因为不能向 trait 对象增加数据。trait 对象并不像其他语言中的对象那么通用:其(trait 对象)具体的作用是允许对通用行为的抽象。 + +示例 17-3 展示了如何定义一个带有 `draw` 方法的 trait `Draw`: 文件名: src/lib.rs @@ -33,13 +32,15 @@ pub trait Draw { } ``` -列表 17-3:`Draw` trait 的定义 +示例 17-3:`Draw` trait 的定义 - +因为第十章已经讨论过如何定义 trait,这看起来应该比较眼熟。接下来就是新内容了:实例 17-4 定义了一个存放了名叫 `components` 的 vector 的结构体 `Screen`。这个 vector 的类型是 `Box`,此为一个 trait 对象:它是 `Box` 中任何实现了 `Draw` trait 的类型的替身。 - - -因为第十章已经讨论过如何定义 trait,这看起来应该比较眼熟。接下来就是新内容了:列表 17-4 有一个名为 `Screen` 的结构体定义,它存放了一个叫做 `components` 的 `Box` 类型的 vector 。`Box` 是一个 trait 对象:它是 `Box` 中任何实现了 `Draw` trait 的类型的替身。 + + 文件名: src/lib.rs @@ -53,9 +54,9 @@ pub struct Screen { } ``` -列表 17-4: 一个 `Screen` 结构体的定义,它带有一个字段`components`,其包含实现了 `Draw` trait 的 trait 对象的 vector +示例 17-4: 一个 `Screen` 结构体的定义,它带有一个字段`components`,其包含实现了 `Draw` trait 的 trait 对象的 vector -在 `Screen` 结构体上,我们将定义一个 `run` 方法,该方法会对其 `components` 上的每一个元素调用 `draw` 方法,如列表 17-5 所示: +在 `Screen` 结构体上,我们将定义一个 `run` 方法,该方法会对其 `components` 上的每一个组件调用 `draw` 方法,如示例 17-5 所示: 文件名: src/lib.rs @@ -77,9 +78,9 @@ impl Screen { } ``` -列表 17-5:在 `Screen` 上实现一个 `run` 方法,该方法在每个 component 上调用 `draw` 方法 +示例 17-5:在 `Screen` 上实现一个 `run` 方法,该方法在每个 component 上调用 `draw` 方法 -这与定义使用了带有 trait bound 的泛型类型参数的结构体不同。泛型类型参数一次只能替代一个具体的类型,而 trait 对象则允许在运行时替代多种具体类型。例如,可以像列表 17-6 那样定义使用泛型和 trait bound 的结构体 `Screen`: +这与定义使用了带有 trait bound 的泛型类型参数的结构体不同。泛型类型参数一次只能替代一个具体类型,而 trait 对象则允许在运行时替代多种具体类型。例如,可以定义 `Screen` 结构体来使用泛型和 trait bound,如示例 17-6 所示: 文件名: src/lib.rs @@ -102,15 +103,15 @@ impl Screen } ``` -列表 17-6: 一种 `Screen` 结构体的替代实现,它的 `run` 方法使用泛型和 trait bound +示例 17-6: 一种 `Screen` 结构体的替代实现,其 `run` 方法使用泛型和 trait bound -这只允许我们拥有一个包含全是 `Button` 类型或者全是 `TextField` 类型的 component 列表的 `Screen` 实例。如果只拥有相同类型的集合,那么使用泛型和 trait bound 是更好的,因为在编译时使用具体类型其定义是单态(monomorphized)的。 +这限制了 `Screen` 实例必须拥有一个全是 `Button` 类型或者全是 `TextField` 类型的组件列表。如果只需要同质(相同类型)集合,则倾向于使用泛型和 trait bound,因为其定义会在编译时采用具体类型进行单态化。 -相反对于存放了 `Vec>` trait 对象的 component 列表的 `Screen` 定义,一个 `Screen` 实例可以存放一个既可以包含 `Box