x-i18n: generated_at: “2026-03-01T14:50:50Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: 09efdf84fb2fd7197a221e8bdd8df24fb7b0a25468b2c84a01a9d71e537f6526 source_path: ch18-01-what-is-oo.md workflow: 16
面向对象语言的特征 (Characteristics of Object-Oriented Languages)
Characteristics of Object-Oriented Languages
编程社区对于一门语言必须具备哪些特性才能被视为面向对象并没有达成共识。Rust 受到了许多编程范式的影响,包括 OOP;例如,我们在第 13 章探讨了源自函数式编程的特性。可以说,OOP 语言共享某些共同特征——即对象、封装和继承。让我们来看看这些特征各自意味着什么,以及 Rust 是否支持它们。
There is no consensus in the programming community about what features a language must have to be considered object oriented. Rust is influenced by many programming paradigms, including OOP; for example, we explored the features that came from functional programming in Chapter 13. Arguably, OOP languages share certain common characteristics—namely, objects, encapsulation, and inheritance. Let’s look at what each of those characteristics means and whether Rust supports it.
对象包含数据和行为 (Objects Contain Data and Behavior)
Objects Contain Data and Behavior
由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著的《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994) 一书(俗称“四人帮” (The Gang of Four) 之书)是面向对象设计模式的目录。它这样定义 OOP:
The book Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (Addison-Wesley, 1994), colloquially referred to as The Gang of Four book, is a catalog of object-oriented design patterns. It defines OOP in this way:
面向对象的程序是由对象组成的。对象包装了数据以及操作这些数据的过程。这些过程通常被称为方法或操作。
Object-oriented programs are made up of objects. An object packages both data and the procedures that operate on that data. The procedures are typically called methods or operations.
根据这个定义,Rust 是面向对象的:结构体和枚举拥有数据,而 impl 块在结构体和枚举上提供方法。尽管带有方法的结构体和枚举不被“称为”对象,但根据“四人帮”对对象的定义,它们提供了相同的功能。
Using this definition, Rust is object oriented: Structs and enums have data,
and impl blocks provide methods on structs and enums. Even though structs and
enums with methods aren’t called objects, they provide the same
functionality, according to the Gang of Four’s definition of objects.
隐藏实现细节的封装 (Encapsulation That Hides Implementation Details)
Encapsulation That Hides Implementation Details
通常与 OOP 相关的另一个方面是“封装 (encapsulation)”的想法,这意味着对象的实现细节对于使用该对象的代码是不可访问的。因此,与对象交互的唯一方式是通过其公有 API;使用对象的代码不应该能够直接触及对象的内部并更改数据或行为。这使得程序员能够更改和重构对象的内部,而无需更改使用对象的代码。
Another aspect commonly associated with OOP is the idea of encapsulation, which means that the implementation details of an object aren’t accessible to code using that object. Therefore, the only way to interact with an object is through its public API; code using the object shouldn’t be able to reach into the object’s internals and change data or behavior directly. This enables the programmer to change and refactor an object’s internals without needing to change the code that uses the object.
我们在第 7 章讨论了如何控制封装:我们可以使用 pub 关键字来决定代码中哪些模块、类型、函数和方法应该是公有的,而默认情况下其他一切都是私有的。例如,我们可以定义一个结构体 AveragedCollection ,它有一个包含 i32 值向量的字段。该结构体还可以有一个包含向量中值平均值的字段,这意味着平均值不必在任何人需要时按需计算。换句话说, AveragedCollection 将为我们缓存计算出的平均值。示例 18-1 包含了 AveragedCollection 结构体的定义。
We discussed how to control encapsulation in Chapter 7: We can use the pub
keyword to decide which modules, types, functions, and methods in our code
should be public, and by default everything else is private. For example, we
can define a struct AveragedCollection that has a field containing a vector
of i32 values. The struct can also have a field that contains the average of
the values in the vector, meaning the average doesn’t have to be computed on
demand whenever anyone needs it. In other words, AveragedCollection will
cache the calculated average for us. Listing 18-1 has the definition of the
AveragedCollection struct.
{{#rustdoc_include ../listings/ch18-oop/listing-18-01/src/lib.rs}}
该结构体被标记为 pub 以便其他代码可以使用它,但结构体内部的字段仍然是私有的。在这种情况下这很重要,因为我们想确保每当向列表中添加或移除值时,平均值也会更新。我们通过在结构体上实现 add 、 remove 和 average 方法来实现这一点,如示例 18-2 所示。
The struct is marked pub so that other code can use it, but the fields within
the struct remain private. This is important in this case because we want to
ensure that whenever a value is added or removed from the list, the average is
also updated. We do this by implementing add, remove, and average methods
on the struct, as shown in Listing 18-2.
{{#rustdoc_include ../listings/ch18-oop/listing-18-02/src/lib.rs:here}}
公有方法 add 、 remove 和 average 是访问或修改 AveragedCollection 实例中数据的唯一方式。当使用 add 方法向 list 添加项或使用 remove 方法移除项时,两者的实现都会调用私有的 update_average 方法,该方法负责同时更新 average 字段。
The public methods add, remove, and average are the only ways to access
or modify data in an instance of AveragedCollection. When an item is added to
list using the add method or removed using the remove method, the
implementations of each call the private update_average method that handles
updating the average field as well.
我们将 list 和 average 字段保留为私有,这样外部代码就无法直接向 list 字段添加或从中移除项;否则,当 list 改变时, average 字段可能会变得不同步。 average 方法返回 average 字段中的值,允许外部代码读取平均值但不能修改它。
We leave the list and average fields private so that there is no way for
external code to add or remove items to or from the list field directly;
otherwise, the average field might become out of sync when the list
changes. The average method returns the value in the average field,
allowing external code to read the average but not modify it.
由于我们已经封装了结构体 AveragedCollection 的实现细节,我们可以很容易地在将来更改诸如数据结构之类的方面。例如,我们可以为 list 字段使用 HashSet<i32> 而不是 Vec<i32> 。只要 add 、 remove 和 average 公有方法的签名保持不变,使用 AveragedCollection 的代码就不需要更改。如果我们改为将 list 设为公有,情况就不一定如此了: HashSet<i32> 和 Vec<i32> 具有不同的添加和移除项的方法,因此如果外部代码直接修改 list ,它可能就不得不进行更改。
Because we’ve encapsulated the implementation details of the struct
AveragedCollection, we can easily change aspects, such as the data structure,
in the future. For instance, we could use a HashSet<i32> instead of a
Vec<i32> for the list field. As long as the signatures of the add,
remove, and average public methods stayed the same, code using
AveragedCollection wouldn’t need to change. If we made list public instead,
this wouldn’t necessarily be the case: HashSet<i32> and Vec<i32> have
different methods for adding and removing items, so the external code would
likely have to change if it were modifying list directly.
如果封装是语言被视为面向对象的一个必要方面,那么 Rust 满足了这一要求。对不同部分的代码选择使用 pub 与否实现了对实现细节的封装。
If encapsulation is a required aspect for a language to be considered object
oriented, then Rust meets that requirement. The option to use pub or not for
different parts of code enables encapsulation of implementation details.
继承作为类型系统和代码共享 (Inheritance as a Type System and as Code Sharing)
Inheritance as a Type System and as Code Sharing
“继承 (Inheritance)”是一种机制,通过它,一个对象可以继承另一个对象定义中的元素,从而获得父对象的数据和行为,而无需你再次定义它们。
Inheritance is a mechanism whereby an object can inherit elements from another object’s definition, thus gaining the parent object’s data and behavior without you having to define them again.
如果一门语言必须具有继承才能被称为面向对象,那么 Rust 就不属于此类语言。如果不使用宏,就无法定义一个继承父结构体字段和方法实现的结构体。
If a language must have inheritance to be object oriented, then Rust is not such a language. There is no way to define a struct that inherits the parent struct’s fields and method implementations without using a macro.
然而,如果你习惯于在你的编程工具箱中使用继承,你可以根据你最初寻求继承的原因,在 Rust 中使用其他解决方案。
However, if you’re used to having inheritance in your programming toolbox, you can use other solutions in Rust, depending on your reason for reaching for inheritance in the first place.
选择继承有两个主要原因。一是代码复用:你可以为一种类型实现特定的行为,而继承使你能够为另一种不同的类型复用该实现。在 Rust 代码中,你可以通过特征方法的默认实现以有限的方式做到这一点,正如你在示例 10-14 中看到的,当时我们在 Summary 特征上添加了 summarize 方法的默认实现。任何实现 Summary 特征的类型都无需更多代码即可使用 summarize 方法。这类似于父类有一个方法的实现,而继承的子类也具有该方法的实现。当实现 Summary 特征时,我们还可以覆盖 summarize 方法的默认实现,这类似于子类覆盖从父类继承的方法实现。
You would choose inheritance for two main reasons. One is for reuse of code:
You can implement particular behavior for one type, and inheritance enables you
to reuse that implementation for a different type. You can do this in a limited
way in Rust code using default trait method implementations, which you saw in
Listing 10-14 when we added a default implementation of the summarize method
on the Summary trait. Any type implementing the Summary trait would have
the summarize method available on it without any further code. This is
similar to a parent class having an implementation of a method and an
inheriting child class also having the implementation of the method. We can
also override the default implementation of the summarize method when we
implement the Summary trait, which is similar to a child class overriding the
implementation of a method inherited from a parent class.
使用继承的另一个原因与类型系统有关:使子类型能够在与父类型相同的地方使用。这也被称为“多态 (polymorphism)”,这意味着如果多个对象共享某些特征,你可以在运行时相互替换它们。
The other reason to use inheritance relates to the type system: to enable a child type to be used in the same places as the parent type. This is also called polymorphism, which means that you can substitute multiple objects for each other at runtime if they share certain characteristics.
多态 (Polymorphism)
Polymorphism
对许多人来说,多态是继承的同义词。但它实际上是一个更通用的概念,指的是可以处理多种类型数据的代码。对于继承,那些类型通常是子类。
To many people, polymorphism is synonymous with inheritance. But it’s actually a more general concept that refers to code that can work with data of multiple types. For inheritance, those types are generally subclasses.
Rust 则使用泛型来对不同的可能类型进行抽象,并使用特征约束来对这些类型必须提供的内容施加限制。这有时被称为“有限制的参数多态 (bounded parametric polymorphism)”。
Rust instead uses generics to abstract over different possible types and trait bounds to impose constraints on what those types must provide. This is sometimes called bounded parametric polymorphism.
Rust 通过不提供继承而选择了一组不同的权衡。继承往往面临共享超出必要代码的风险。子类不应该总是共享其父类的所有特征,但使用继承时却会如此。这可能使程序的设计缺乏灵活性。它还引入了在子类上调用没有意义或由于方法不适用于子类而导致错误的可能性。此外,有些语言只允许“单继承”(意味着子类只能继承自一个类),进一步限制了程序设计的灵活性。
Rust has chosen a different set of trade-offs by not offering inheritance. Inheritance is often at risk of sharing more code than necessary. Subclasses shouldn’t always share all characteristics of their parent class but will do so with inheritance. This can make a program’s design less flexible. It also introduces the possibility of calling methods on subclasses that don’t make sense or that cause errors because the methods don’t apply to the subclass. In addition, some languages will only allow single inheritance (meaning a subclass can only inherit from one class), further restricting the flexibility of a program’s design.
出于这些原因,Rust 采取了不同的方法,使用特征对象而不是继承来实现运行时的多态性。让我们来看看特征对象是如何工作的。
For these reasons, Rust takes the different approach of using trait objects instead of inheritance to achieve polymorphism at runtime. Let’s look at how trait objects work.