mirror of
https://github.com/gnu4cn/rust-lang-zh_CN.git
synced 2026-08-19 12:43:28 +08:00
Updated 'src/oop/characteristics_oop'.
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
[package]
|
||||
name = "encapsulation_demo"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_variables)]
|
||||
|
||||
pub struct AveragedCollection {
|
||||
list: Vec<i32>,
|
||||
average: f64,
|
||||
}
|
||||
|
||||
impl AveragedCollection {
|
||||
pub fn new () -> AveragedCollection {
|
||||
AveragedCollection {
|
||||
list: vec![],
|
||||
average: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, value: i32) {
|
||||
self.list.push(value);
|
||||
self.update_average();
|
||||
|
||||
12
projects/encapsulation_demo/src/main.rs
Normal file
12
projects/encapsulation_demo/src/main.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
use encapsulation_demo::AveragedCollection;
|
||||
|
||||
fn main() {
|
||||
let mut col = AveragedCollection::new();
|
||||
|
||||
col.add(10);
|
||||
col.add(100);
|
||||
col.add(100);
|
||||
col.add(100);
|
||||
println! ("{}", col.average());
|
||||
|
||||
}
|
||||
@@ -114,7 +114,7 @@
|
||||
- [未来值、任务与线程](async/all_together.md)
|
||||
|
||||
|
||||
- [Rust 的面向对象编程特性](Ch17_Object_Oriented_Programming_Features_of_Rust.md)
|
||||
- [面向对象编程特性](Ch17_Object_Oriented_Programming_Features_of_Rust.md)
|
||||
- [面向对象语言的特征](oop/characteristics_oop.md)
|
||||
- [使用特质对象抽象共用行为](oop/trait_objects.md)
|
||||
- [实现一种面向对象设计模式](oop/implementing.md)
|
||||
|
||||
@@ -1,32 +1,24 @@
|
||||
# 面向对象语言的特征
|
||||
|
||||
**Characteristics of Object-Oriented Languages**
|
||||
在编程界,对于某门语言必需具备哪些特征才能被视为面向对象并无定论。Rust 受了许多编程范式,programming paradigms,的影响,包括 OOP;例如,我们在第 13 章中探讨了来自函数式编程的特性。可以说,OOP 语言共用一些共同特征 -- 即对象、封装和继承。我们来看看这些特征各自的含义,以及 Rust 是否支持他们。
|
||||
|
||||
|
||||
在编程界,并无关于某门被视为面向对象的,而必须具有哪些特性的共识。Rust 受了许多编程范式,programming paradigms,的影响,其中就包括 OOP;比如在第 13 章中,咱们就曾探讨过,那些来自于函数式编程的特性。可以说,那些 OOP 的语言,确实是共用了一些确切的特征的,那即是对象、封装与继承等。下面就来看看这些特征各自指的是什么,以及 Rust 是否支持他们。
|
||||
## 对象包含数据和行为
|
||||
|
||||
Erich Gamma、Richard Helm、Ralph Johnson 及 John Vlissides 等的合著 *[Design Patterns: Elements of Reusable Object-Oriented Software](https://en.wikipedia.org/wiki/Design_Patterns)* (Addison-Wesley Professional, 1994),又被通俗地叫做 *The Gang of Four* 书,是面向对象设计模式的汇编。该书是这样定义 OOP 的:
|
||||
|
||||
> “面向对象的程序对象所组成。**对象** 封装了数据和操作该数据的过程。这些过程通常称为 **方法** 或 **操作**”。
|
||||
|
||||
使用这个定义,那么 Rust 是面向对象的:结构体和枚举包含数据,而 `impl` 代码块提供结构体和枚举上的方法。尽管带有方法的结构体与枚举未 *被称作* 对象,但根据 The Gang of Four 对对象的定义,他们提供了同样的功能。
|
||||
|
||||
|
||||
## 对象包含了数据及行为
|
||||
## 隐藏实现细节的封装
|
||||
|
||||
**Objects Contain Data and Behavior**
|
||||
通常与 OOP 相关的另一方面是,*封装,encapsulation* 的思想,这意味着使用对象的代码无法访问对象的实现细节。因此,与对象交互的唯一方式是,通过对象的公开 API;使用对象的代码不应能够深入对象内部,并直接更改数据或行为。这使得程序员能够修改和重构对象的内部结构,而无需更改使用对象的代码。
|
||||
|
||||
我们在第 7 章中讨论过怎样控制封装:我们可以使用 `pub` 关键字来决定代码中哪些模组、类型、函数与方法等应为公开的,默认情况下其他所有项目都是私有的。例如,我们可以定义一个 `AveragedCollection` 结构体,有着一个包含 `i32` 值矢量的字段。该结构体还可以有一个包含矢量中值的平均数的字段,这意味着表示在任何人需要时都不必按需计算平均值。换句话说,`AveragedCollection` 将为我们缓存计算出的平均值。下面清单 18-1 提供了 `AveragedCollection` 结构体的定义。
|
||||
|
||||
Erich Gamma、Richard Helm、Ralph Johnson 及 John Vlissides 等的合著 *Design Patterns: Elements of Reusable Object-Oriented Software* (Addison-Wesley Professional, 1994),又被通俗地叫做 *The Gang of Four* 书,便是面向对象设计模式的一个目录。该书像下面这样定义了 OOP:
|
||||
|
||||
> 面向对象程序是由对象所组成的。*对象,an object* 同时打包了数据与运行在那数据上的过程。这些过程一般就叫做 *方法,methods* 或 *操作,operations*。
|
||||
|
||||
运用这个定义,Rust 便是面向对象的:结构体与枚举均有着数据,而 `impl` 块则提供了结构体与枚举上的那些方法。即使有着方法的那些结构体与枚举未*被称作* 对象,根据 The Gang of Four 的对象定义,他们提供了同样的功能。
|
||||
|
||||
|
||||
## 隐藏了实现细节的封装
|
||||
|
||||
**Encapsulation that Hides Implementation Details**
|
||||
|
||||
|
||||
通常与 OOP 相关的另一方面的 *封装,encapsulation*,是指对于用到该对象的代码,对象实现细节是不可访问的。由此,与对象交互的唯一方式,便是经由该对象的公开 API;运用对象的代码,不应具备到达该对象内部,而直接改变数据或行为的能力。这实现了程序员在无需修改用到对象的那些代码之下,修改或重构对象的那些内部代码。
|
||||
|
||||
在第 7 章中,咱们曾讨论过怎样控制封装:咱们可以使用 `pub` 关键字,来决定咱们代码中,哪些模组、类型、函数与方法等应为公开的,而默认其他所有项目都是私有的。比如,咱们就可以定义有着包含 `i32` 值矢量的一个字段的 `AveragedCollection` 结构体。这个字段也可以有包含着那个矢量中值的平均数的一个字段,表示在有人需要该平均值时,不必按需计算出该平均值。换句话说,`AveragedCollection` 将为咱们缓存这个计算出的平均值。下面清单 17-1 便有着这个 `AveragedCollection` 结构体的定义:
|
||||
|
||||
<a name="listing_18-1"></a>
|
||||
文件名:`src/lib.rs`
|
||||
|
||||
```rust
|
||||
@@ -36,10 +28,11 @@ pub struct AveragedCollection {
|
||||
}
|
||||
```
|
||||
|
||||
*清单 17-1:维护着一个整数清单及该集合中项目平均数的 `AveragedCollection` 结构体*
|
||||
**清单 18-1**:`AveragedCollection` 结构体,维护一个整数列表和集合中项目的平均数
|
||||
|
||||
该结构体被标记为 `pub`,从而其他代码就可以使用他,而该结构体内部的那些字段保持着私有。由于咱们打算不管何时在有某个值被添加到清单,或从清单移除时,其中的平均数也要同时被更新,因此在这个示例中这样的封装就很重要。咱们是通过实现下面清单 17-2 中所给出的 `add`、`remove` 及 `average` 方法,做到这一点的。
|
||||
该结构体被标记为 `pub`,以便其他代码可以使用他,但结构体内的字段仍保持私有。在这一情形下这一点很重要,因为我们希望确保每当有值添加到列表或从列表中移除时,平均数也会随之更新。我们通过在该结构体上实现 `add`、`remove` 和 `average` 方法来实现这一点,如下清单 18-2 中所示。
|
||||
|
||||
<a name="listing_18-2"></a>
|
||||
文件名:`src/lib.rs`
|
||||
|
||||
```rust
|
||||
@@ -71,43 +64,38 @@ impl AveragedCollection {
|
||||
}
|
||||
```
|
||||
|
||||
*清单 17-2:`AveragedCollection` 上公开方法 `add`、`remove` 与 `average` 的实现*
|
||||
**清单 18-2**:`AveragedCollection` 上的公开方法 `add`、`remove` 与 `average` 的实现
|
||||
|
||||
这些公开方法 `add`、`remove` 与 `average`,是仅有的访问或修改 `AveragedCollection` 实例中数据的方式。在使用 `add` 方法或 `remove` 方法,添加或移除某个条目时,其各自的实现,就同时会调用处理更新 `average` 字段的私有 `update_average` 方法。
|
||||
`add`、`remove` 与 `average` 这三个公开方法 ,是访问或修改 `AveragedCollection` 实例中数据的唯一方式。当使用 `add` 方法添加项目到 `list`,或使用 `remove` 方法移除项目时,两个方法调用的实现还会调用私有的`update_average` 方法,其会负责更新 `average`字段。
|
||||
|
||||
咱们把 `list` 与 `average` 自动留着私有,从而外部代码就无法直接添加项目到那个 `list`,或直接从那个 `list` 移除项目;不然的话,在那个`list` 变化时,`average` 字段就可能失去同步。其中的 `average` 方法,返回的是 `average` 字段中的值,这实现了外部代码读取那个 `average` 而不会修改他。
|
||||
我们把 `list` 和 `average` 字段保留为私有,以便外部代码无法直接向 `list` 字段添加项目或从中移除项目;否则,当 `list` 发生变化时,`average` 字段就会失去同步。`average` 方法返回 `average` 字段中的值,允许外部代码读取 `average` 但不能修改他。
|
||||
|
||||
由于咱们已封装了结构体 `AveragedCollection` 的实现细节,因此咱们就可以在将来轻易地修改各个方面,诸如数据结构等。比如,咱们可以对其中的 `list` 字段,使用 `HashSet<i32>` 而非 `Vec<i32>`。只要 `add`、`remove` 及 `average` 三个公开方法的签名保持不变,那些使用 `AveragedCollection` 的代码就无需改变。而相反若咱们把 `list` 构造为公开,就未必如此了:`HashSet<i32>` 与 `Vec<i32>` 有着添加和一处条目的不同方法,由此在外部代码直接修改 `list` 时,就大概率不得不修改了。
|
||||
由于我们已经封装了结构体 `AveragedCollection` 的实现细节,因此将来可以轻松地修改数据结构等方面。例如,我们可以对 `list` 字段使用 `HashSet<i32>` 而非 `Vec<i32>`。只要 `add`、`remove` 及 `average` 三个公开方法的签名保持不变,使用 `AveragedCollection` 的代码就无需修改。若我们转而构造 `list` 为公开,情况就未必如此:`HashSet<i32>` 与 `Vec<i32>` 有着添加和移除项目的不同方法,因此如果外部代码直接修改 `list`,很可能就必须修改。
|
||||
|
||||
若封装是某门语言被视为面向对象的要件,你们 Rust 是满足那种要求的。对代码的不同部分,使用抑或不使用 `pub` 的选项,实现了实现细节的封装。
|
||||
当封装是某门语言被视为面向对象的必要条件,那么 Rust 满足这一要求。对代码的不同部分使用或不使用 `pub` 的选项,实现了对实现细节的封装。
|
||||
|
||||
|
||||
## 以类型系统及以代码共用的继承
|
||||
## 作为类型系统和代码共用的继承
|
||||
|
||||
**Inheritance as a Type System and as Code Sharing**
|
||||
*继承,inheritance* 属于一种机制,一个对象可以从另一对象的定义继承元素,从而获得父对象的数据和行为,而无需再次定义他们。
|
||||
|
||||
若某门语言必须具备继承,才能被称为面向对象语言,那么 Rust 就不是这样的语言。在不使用宏的情况下,就无法定义继承父结构体的字段和方法实现的结构体。
|
||||
|
||||
*继承,inheritance*,乃籍以实现对象从另一对象继承一些元素,从而在不必再度定义这些元素之下,获得父辈对象数据与行为的一种机制。
|
||||
不过,若咱们于在编程工具箱中使用继承,那么在 Rust 中也可以采用其他解决方案,具体取决于咱们最初选择继承的原因。
|
||||
|
||||
若某们语言务必要有着继承,方能成为一门面向对象语言,那么 Rust 就不算是面向对象语言。在不使用宏,a macro 之下,没有定义出继承父辈结构体字段与方法实现的结构体的方法。
|
||||
咱们选择继承出于两个主要原因。一是为了代码的重用:咱们可以为一种类型实现特定的行为,而继承使咱们可以针对不同的类型重用该实现。咱们可以使用默认特质方法实现来有限地实现这一点,正如咱们在 [清单 10-14](../generic_types_traits_and_lifetimes/traits.md#listing_10-14) 中所见,当时我们对 `Summary` 特质添加了 `summarize` 方法的默认实现在。任何实现 `Summary` 特质的类型,都将于其上有着可用的 `summarize` 方法,而无需更多代码。这类似于父类有着某个方法的实现,继承子类也会有着该方法的实现。当我们实现 `Summary` 特质时,也可以重写 `summarize` 方法的默认实现,这类似于子类重写从父类继承的方法实现。
|
||||
|
||||
然而,若在编程工具箱中惯于使用继承,那么依据咱们将继承作为头等大事的自身理由,是可以运用 Rust 中别的一些办法的。
|
||||
使用继承的另一个原因与类型系统相关:使子类型可以在父类型相同的位置使用。这也称为 *多态,polymorphism*,这意味着当多个对象共用某些特征时,咱们可以在运行时相互替换多个对象。
|
||||
|
||||
之所以选用继承,大致有两种原因。一个是代码的重用:咱们可以对一个类型实现一些特定行为,而继承就让咱们可以对另一类型重用那些实现。咱们可以使用一些默认的特质方法实现,即咱们曾在清单 10-14 中,将 `summarize` 方法的一个默认实现,添加到 `Summary` 特质上时所见到的那样,在 Rust 代码中以一种受限方式做到这点。任何实现了这个 `Summary` 特质的类型,在无需更多代码之下,都将在其上有着这个 `summarize` 方法。这与父类有着某个方法的实现,同时集成的子类也会有着该方法的实现是类似的。在实现这个 `Summary` 特质时,咱们也可以重写 `summarize` 方法的默认实现,这与子类重新继承自父类的方法实现类似。
|
||||
|
||||
而使用与类型系统相关继承的另一原因:即为了实现在与父类型的同一地方,使用子类型。这又被成为 *多态,polymorphism*,是指在多个对象共用了一些确切特征时,咱们可以相互替换使用他们。
|
||||
|
||||
> **关于多态**
|
||||
> 关于 **多态**
|
||||
>
|
||||
> 对许多人来讲,多态等同于继承。但他实际上指的是代码可工作于多个类型数据之下的一个宽泛概念。而对于继承,这些类型则是通用的一些子类。
|
||||
> 对许多人来说,多态与继承同义。但他实际上是个更普遍的概念,指的是可以处理多种数据类型的代码。在继承中,这些类型通常属于子类。
|
||||
>
|
||||
> Rust 则运用了泛型,来对各异的各种可能类型加以抽象,并使用特质边界来强化这些类型所必须提供的那些约束。有时这样的做法,又被叫做 *有边界的参数化多态,bounded parametric polymorphism*。
|
||||
> Rust 则使用泛型来抽象各种可能的类型,并使用特质边界对这些类型必须提供的内容施加约束。这有时被称为 *有界参数多态性,bounded parametric polymorphism*。
|
||||
|
||||
由于继承通常有着共用了超出必要代码的风险,时至今日,其已在许多编程语言中,作为编程设计模式而失宠了。子类本不应共用其父类的全部特征,但在继承父类时却会这样做。这就会造成程序的设计有较低的灵活性。由于子类从父类继承的一些方法并不适用于子类,因此继承还会引入调用子类上无意义或引发错误方法的可能。此外,一些语言还只将运行单一继承(即子类只能从一个类继承),这进一步限制了程序设计的灵活度。
|
||||
Rust 通过不提供继承,做出了不同的权衡取舍。继承往往存在共用过多代码的风险。子类并不总是应该共用父类的所有特征,但在继承下却要这样做。这会降低程序设计的灵活性。由于某些方法并不适用于子类,他还引入了调用子类上不合理的方法,或引发错误的方法的可能性。此外,一些语言仅支持 *单一继承*(即子类只能继承自一个类),这进一步限制了程序设计的灵活性。
|
||||
|
||||
由于这些原因,Rust 便采取了运用特质对象,而非继承的方法。接下来就要看看特质对象是如何实现 Rust 中的多态。
|
||||
出于这些原因,Rust 采取了不同的方法,即使用特质对象而不是继承,来实现运行时的多态性。我们来看看特质对象的工作原理。
|
||||
|
||||
|
||||
(End)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user