❝外界有多纷扰,内心就有多丰饶❞
大家好,我是「柒八九」。
今天,我们继续「Rust学习笔记」的探索。我们来谈谈关于「面向对象编程」的相关知识点。
如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。
文章list
- Rust学习笔记之Rust环境配置和入门指南
- Rust学习笔记之基础概念
- Rust学习笔记之所有权
- Rust学习笔记之结构体
- Rust学习笔记之枚举和匹配模式
- Rust学习笔记之包、Crate和模块
- Rust学习笔记之集合
- Rust学习笔记之错误处理
- Rust学习笔记之泛型、trait 与生命周期
- Rust学习笔记之闭包和迭代器
- Rust学习笔记之智能指针
- Rust学习笔记之并发
你能所学到的知识点
- 编程范式 「推荐阅读指数」 ⭐️⭐️⭐️⭐️
- 面向对象编程 「推荐阅读指数」 ⭐️⭐️⭐️⭐️⭐️
- 面向对象在Rust中的体现 「推荐阅读指数」 ⭐️⭐️⭐️⭐️⭐️❞
好了,天不早了,干点正事哇。
编程范式
❝「编程范式」是一种编程的方法论或思维模式,它定义了一组规则、原则和约束,用于组织和结构化程序的设计和实现。 ❞
不同的编程范式提供了不同的思考方式和工作方式,以解决特定类型的问题和应对不同的编程场景。每种编程范式都基于一套独特的概念和原则,它们描述了程序员如何构建、组织和操作代码。「编程范式指导程序员思考问题的方式,并提供了特定的语法和语义规则来实现代码」。通过遵循特定的编程范式,程序员可以更好地组织代码、提高代码的可读性、可维护性和重用性。
常见的编程范式
编程范式 | 适用场景 | 主要编程语言 |
---|---|---|
面向过程编程(Procedural Programming) | 基于过程和函数的编程范式,通过定义一系列的过程和函数来实现程序的逻辑 | C、Fortran、Pascal |
面向对象编程(Object-Oriented Programming,OOP) | 将「现实世界的事物抽象成对象」,通过对象之间的交互和消息传递来实现程序的设计和开发。 | Java、C 、Python、Ruby |
函数式编程(Functional Programming) | 将「计算视为函数求值的过程,强调无状态和不可变数据的使用」,避免副作用,注重函数的组合和高阶函数的使用 | Haskell、Lisp、Clojure、Scala |
声明式编程(Declarative Programming) | 描述问题的逻辑和规则,而不是明确指定解决问题的步骤,更「关注"是什么"而非"如何"」 | SQL、Prolog、XSLT、Haskell |
响应式编程(Reactive Programming) | 基于「数据流和事件」的编程范式,通过触发和响应事件来处理数据流和状态变化。前端开发、事件驱动、用户界面交互、异步数据流处理 | JavaScript、RxJava、React |
过程式编程(Procedural Programming) | 按照顺序执行指令的编程方式,「强调控制流和算法的设计」 | Bash、Perl、Lua、Batch |
指令式编程(Imperative Programming) | 通过一系列的指令和语句来描述计算机的操作,强调修改和控制计算机状态。 | Assembly、C、C |
❝需要注意的是,编程范式并「不是互斥的,而是可以相互融合和组合使用的」。 ❞
例如,可以在面向对象编程中结合函数式编程的思想,或者在声明式编程中嵌入面向对象的概念。这种灵活的组合可以根据问题的需求和开发团队的偏好来决定。
面向对象编程
面向对象编程Object-Oriented Programming(OOP
)是一种流行的编程范式,它以对象为核心,将数据和操作数据的方法封装在一起。面向对象编程强调对象之间的交互和消息传递,通过定义类、创建对象、继承和多态等概念来实现程序的设计和开发。
以下是面向对象编程的核心概念:
概念 | 描述 |
---|---|
对象 (Object) | 面向对象编程的「基本单元」,是类的实例,具有自己的状态和行为。 |
类 (Class) | 定义对象的模板或蓝图,「描述了对象的属性和行为」。 |
封装 (Encapsulation) | 将数据和操作数据的方法封装在类中,「隐藏内部实现细节,提供公共接口」。 |
继承 (Inheritance) | 允许一个类继承另一个类的属性和方法,「实现代码的重用和扩展」。 |
多态 (Polymorphism) | 「同一个方法可以根据对象的不同表现出不同的行为」,提高代码的灵活性。 |
抽象 (Abstraction) | 将问题简化为类、对象和方法的模型,提取关键特征和行为形成可重用模型。 |
信息隐藏 (Information Hiding) | 将类的内部实现细节对外部隐藏,保护对象的状态,通过公共接口进行操作。 |
消息传递 (Message Passing) | 通过对象之间的消息传递实现交互,对象可以发送消息请求执行方法。 |
❝对象是类的实例,类定义了对象的属性和行为。封装将数据和方法封装在类中,继承允许类继承另一个类的属性和方法。多态使得同一个方法可以根据对象的不同表现出不同行为。抽象将问题简化为模型,信息隐藏保护对象的状态。消息传递实现对象之间的交互和协作。 ❞
面向对象编程具有以下优点:
- 可重用性:通过封装、继承和多态,可以实现代码的重用,减少开发工作量。
- 可维护性:面向对象编程具有模块化和组件化的特性,使代码更易于理解、修改和扩展。
- 灵活性:多态和抽象概念
面向对象在Rust中的体现
❝面向对象编程语言所共享的一些特性往往是对象Object、封装Encapsulation和继承Inheritance。 ❞
对象包含数据和行为
Design Patterns
是面向对象编程模式的目录。它这样定义面向对象编程:
❝面向对象的程序是「由对象组成」的。一个「对象」包含数据和操作这些数据的过程。这些过程通常被称为「方法」或「操作」。 ❞
在这个定义下,Rust
是面向对象的:
❝「结构体」和「枚举」包含数据而
impl
块提供了在结构体和枚举之上的「方法」。虽然带有方法的结构体和枚举并不被称为「对象」,但是他们提供了与对象相同的功能。 ❞
封装隐藏了实现细节
另一个通常与面向对象编程相关的方面是 封装Encapsulation的思想:「对象的实现细节不能被使用对象的代码获取到」。所以唯一与对象交互的方式是通过对象提供的「公有 API」;使用对象的代码无法深入到对象内部并直接改变数据或者行为。封装使得改变和重构对象的内部时无需改变使用对象的代码。
❝在
Rust
中,可以使用pub
关键字来决定模块、类型、函数和方法是公有的,而「默认情况下其他一切都是私有的」。 ❞
可以定义一个包含一个 i32
类型 vector
的结构体 AveragedCollection
。结构体也可以有一个字段,该字段保存了 vector
中所有值的平均值。
pub struct AveragedCollection {
list: Vec<i32>,
average: f64,
}
结构体自身被标记为 pub
,这样其他代码就可以使用这个结构体,但是在「结构体内部的字段仍然是私有的」。
还可以在结构体上实现 add
、remove
和 average
方法。保证变量被增加到列表或者被从列表删除时,也会同时更新平均值。
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;
}
}
公有方法 add
、remove
和 average
是修改 AveragedCollection
实例的唯一方式。当使用 add
方法把一个元素加入到 list
或者使用 remove
方法来删除时,这些方法的实现同时会调用私有的 update_average
方法来更新 average
字段。
继承,作为类型系统与代码共享
❝继承Inheritance是一个很多编程语言都提供的机制,「一个对象可以定义为继承另一个对象的定义,这使其可以获得父对象的数据和行为,而无需重新定义」。 ❞
如果一个语言必须有继承才能被称为面向对象语言的话,那么 Rust
就不是面向对象的。无法定义一个结构体继承父结构体的成员和方法。
选择继承有两个主要的原因。
❝
- 第一个是为了「重用代码」:一旦为一个类型实现了特定行为,继承可以对一个「不同的类型」重用这个实现。相反
Rust
代码可以使用默认trait
方法实现来进行共享。 - 第二个使用继承的原因与「类型系统」有关:表现为子类型可以用于父类型被使用的地方。这也被称为 多态polymorphism,这意味着如果多种对象共享特定的属性,则可以相互替代使用。
❞
近来继承作为一种语言设计的解决方案在很多语言中失宠了,因为其时常带有「共享多于所需」的代码的风险。「子类不应总是共享其父类的所有特征,但是继承却始终如此」。如此会使程序设计更为不灵活,并引入无意义的子类方法调用,或由于方法实际并不适用于子类而造成错误的可能性。某些语言还只允许子类继承一个父类,进一步限制了程序设计的灵活性。
因为这些原因,Rust
选择了一个不同的途径,使用 trait
对象而不是继承。
为使用不同类型的值而设计的 trait 对象
Rust
刻意不将结构体与枚举称为 “对象”,以便与其他语言中的对象相区别。在结构体或枚举中,结构体字段中的数据和 impl
块中的行为是分开的,不同于其他语言中将数据和行为组合进一个称为对象的概念中。trait
对象将数据和行为两者相结合,从这种意义上说 则 其更类似其他语言中的对象。不过 trait
对象不同于传统的对象,因为不能向 trait
对象增加数据。trait
对象并不像其他语言中的对象那么通用:其(trait
对象)具体的作用是「允许对通用行为进行抽象」。
后记
「分享是一种态度」。
参考资料:《Rust权威指南》