Rust学习笔记之面向对象编程

2023-08-01 16:44:43 浏览数 (1)

❝外界有多纷扰,内心就有多丰饶❞

大家好,我是「柒八九」

今天,我们继续「Rust学习笔记」的探索。我们来谈谈关于「面向对象编程」的相关知识点。

如果,想了解该系列的文章,可以参考我们已经发布的文章。如下是往期文章。

文章list

  1. Rust学习笔记之Rust环境配置和入门指南
  2. Rust学习笔记之基础概念
  3. Rust学习笔记之所有权
  4. Rust学习笔记之结构体
  5. Rust学习笔记之枚举和匹配模式
  6. Rust学习笔记之包、Crate和模块
  7. Rust学习笔记之集合
  8. Rust学习笔记之错误处理
  9. Rust学习笔记之泛型、trait 与生命周期
  10. Rust学习笔记之闭包和迭代器
  11. Rust学习笔记之智能指针
  12. Rust学习笔记之并发

你能所学到的知识点

  1. 编程范式 「推荐阅读指数」 ⭐️⭐️⭐️⭐️
  2. 面向对象编程 「推荐阅读指数」 ⭐️⭐️⭐️⭐️⭐️
  3. 面向对象在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)

通过对象之间的消息传递实现交互,对象可以发送消息请求执行方法。

❝对象是类的实例,类定义了对象的属性和行为。封装将数据和方法封装在类中,继承允许类继承另一个类的属性和方法。多态使得同一个方法可以根据对象的不同表现出不同行为。抽象将问题简化为模型,信息隐藏保护对象的状态。消息传递实现对象之间的交互和协作。 ❞

面向对象编程具有以下优点:

  1. 可重用性:通过封装、继承和多态,可以实现代码的重用,减少开发工作量。
  2. 可维护性:面向对象编程具有模块化和组件化的特性,使代码更易于理解、修改和扩展。
  3. 灵活性:多态和抽象概念

面向对象在Rust中的体现

❝面向对象编程语言所共享的一些特性往往是对象Object、封装Encapsulation和继承Inheritance。 ❞

对象包含数据和行为

Design Patterns是面向对象编程模式的目录。它这样定义面向对象编程:

❝面向对象的程序是「由对象组成」的。一个「对象」包含数据和操作这些数据的过程。这些过程通常被称为「方法」「操作」。 ❞

在这个定义下,Rust 是面向对象的:

「结构体」「枚举」包含数据而 impl 块提供了在结构体和枚举之上的「方法」。虽然带有方法的结构体和枚举并不被称为「对象」,但是他们提供了与对象相同的功能。 ❞


封装隐藏了实现细节

另一个通常与面向对象编程相关的方面是 封装Encapsulation的思想:「对象的实现细节不能被使用对象的代码获取到」。所以唯一与对象交互的方式是通过对象提供的「公有 API」;使用对象的代码无法深入到对象内部并直接改变数据或者行为。封装使得改变和重构对象的内部时无需改变使用对象的代码。

❝在Rust中,可以使用 pub 关键字来决定模块、类型、函数和方法是公有的,而「默认情况下其他一切都是私有的」。 ❞

可以定义一个包含一个 i32 类型 vector 的结构体 AveragedCollection 。结构体也可以有一个字段,该字段保存了 vector 中所有值的平均值。

代码语言:javascript复制
pub struct AveragedCollection {
    list: Vec<i32>,
    average: f64,
}

结构体自身被标记为 pub,这样其他代码就可以使用这个结构体,但是在「结构体内部的字段仍然是私有的」

还可以在结构体上实现 addremoveaverage 方法。保证变量被增加到列表或者被从列表删除时,也会同时更新平均值。

代码语言:javascript复制
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;
    }
}

公有方法 addremoveaverage 是修改 AveragedCollection 实例的唯一方式。当使用 add 方法把一个元素加入到 list 或者使用 remove 方法来删除时,这些方法的实现同时会调用私有的 update_average 方法来更新 average 字段。

继承,作为类型系统与代码共享

❝继承Inheritance是一个很多编程语言都提供的机制,「一个对象可以定义为继承另一个对象的定义,这使其可以获得父对象的数据和行为,而无需重新定义」。 ❞

如果一个语言必须有继承才能被称为面向对象语言的话,那么 Rust 就不是面向对象的。无法定义一个结构体继承父结构体的成员和方法。

选择继承有两个主要的原因。

  • 第一个是为了「重用代码」:一旦为一个类型实现了特定行为,继承可以对一个「不同的类型」重用这个实现。相反 Rust 代码可以使用默认 trait 方法实现来进行共享。
  • 第二个使用继承的原因与「类型系统」有关:表现为子类型可以用于父类型被使用的地方。这也被称为 多态polymorphism,这意味着如果多种对象共享特定的属性,则可以相互替代使用。

近来继承作为一种语言设计的解决方案在很多语言中失宠了,因为其时常带有「共享多于所需」的代码的风险。「子类不应总是共享其父类的所有特征,但是继承却始终如此」。如此会使程序设计更为不灵活,并引入无意义的子类方法调用,或由于方法实际并不适用于子类而造成错误的可能性。某些语言还只允许子类继承一个父类,进一步限制了程序设计的灵活性。

因为这些原因,Rust 选择了一个不同的途径,使用 trait 对象而不是继承。

为使用不同类型的值而设计的 trait 对象

Rust 刻意不将结构体与枚举称为 “对象”,以便与其他语言中的对象相区别。在结构体或枚举中,结构体字段中的数据和 impl 块中的行为是分开的,不同于其他语言中将数据和行为组合进一个称为对象的概念中。trait 对象将数据和行为两者相结合,从这种意义上说 则 其更类似其他语言中的对象。不过 trait 对象不同于传统的对象,因为不能向 trait 对象增加数据。trait 对象并不像其他语言中的对象那么通用:其(trait 对象)具体的作用是「允许对通用行为进行抽象」

后记

「分享是一种态度」

参考资料:《Rust权威指南》

0 人点赞