三大特征
三大特征
面向对象三大特征:继承、多态、封装/隐藏
Rust的面向对象与其他语言的大不相同,三大特征也是
封装
首先就是数据和方法不放在一起,而是对结构体进行方法的追加。语法上更类似于 typescript 的 interface declare
优缺点: (个人认为)
- 优点
- 更灵活,随时追加新方法
- 容易规范对象结构大小
- 缺点
- 声明不集中
- 分类只能按成员变量和方法分类,不能按功能分类 (这是一种偏好写法)
struct Circle {
x: f64,
y: f64,
radius: f64,
}
impl Circle {
// new是Circle的关联函数,因为它的第一个参数不是self,且new并不是关键字
// 这种方法往往用于初始化当前结构体的实例
fn new(x: f64, y: f64, radius: f64) -> Circle {
Circle {
x: x,
y: y,
radius: radius,
}
}
// Circle的方法,&self表示借用当前的Circle结构体
fn area(&self) -> f64 {
std::f64::consts::PI * (self.radius * self.radius)
}
}
继承
参考:
- https://www.cnblogs.com/redclock/p/4995954.html
- https://rust-book.junmajinlong.com/ch11/03_trait_inherite.html
多态
文章1
封装(encapsulation)
与其他语言比较
rust 的封装是基于结构体,而不是对象,结构体就是 rust 的对象,这个和go一样。
默认情况下,结构体只有字段。可以通过实现结构体,来为结构体添加方法,也是和go一样的特性!!
注意,结构体自身被标记为 pub,这样其他代码就可以使用这个结构体,但是在结构体内部的字段仍然是私有的!!!!
pub struct AveragedCollection {
list: Vec<i32>,
average: f64,
}
impl AveragedCollection {
// 这个 self 是不是有熟悉的感觉,python 中大量使用 self
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;
}
}
fn main() {
let mut average = AveragedCollection {
list: vec![1, 2, 3],
average: 3.0,
};
// 注意看,这个里用 . 而不是 ::,在rust中对象使用是. 进行调用
average.add(3);
}
继承(Inheritance)-- rust 没有继续机制!!
与其他语言比较
是一个很多编程语言都提供的机制,一个对象可以定义为继承另一个对象定义中的元素,这使其可以获得父对象的数据和行为,而无需重新定义。
如果一个语言必须有继承才能被称为面向对象语言的话,那么 Rust 就不是面向对象的。因为没有宏则无法定义一个结构体继承父结构体的成员和方法。
其实 go 也是这么干的,这两哥们都不愿意使用继承,而都是通过组合的形式。
rust 如何实现代码复用?
Rust 代码中可以使用默认 trait 方法实现来进行有限的共享。
多态(Polymorphism)
Rust 则通过泛型来对不同的可能类型进行抽象,并通过 trait bounds (特征约束) 对这些类型所必须提供的内容施加约束。
这有时被称为 bounded parametric polymorphism (有界参数多态性)。
Rust 选择了一个不同的途径,使用 trait 对象而不是继承。
其实 trait 就是类似于其它语言的接口(Interface),然后以这种形式实现多态。
举个例子:
定义一下Shape
的Trait,其中包含一个area
方法。
然后,分别为Circle
和Rectangle
结构体实现了Shape Trait。
最后,编写了一个print_area
函数,它接受一个实现了Shape Trait的参数,并打印出其面积。
通过Trait和动态分发,可以在运行时选择不同的实现,并实现多态的效果。
trait Shape { // 图形 (特征)
fn area(&self) -> f64;
}
struct Circle { // 圆形
radius: f64,
}
impl Shape for Circle { // 圆形拥有图形特征
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
struct Rectangle { // 矩形
width: f64,
height: f64,
}
impl Shape for Rectangle { // 矩形拥有图形特征
fn area(&self) -> f64 {
self.width * self.height
}
}
fn print_area(shape: &dyn Shape) {
println!("Area: {}", shape.area());
}
fn main() {
let circle = Circle { radius: 1.0 };
let rectangle = Rectangle { width: 2.0, height: 3.0 };
print_area(&circle);
print_area(&rectangle);
}
这个其实和go实现多态的方式是一样的,也是基于实现接口的方式。
不过话说trait好像是类似于纯抽象基类的东西
即:无自带数据、也无自实现
总结,为什么不基于继承
与其他语言比较
Rust 和 go 这两哥们都 不支持 基于继承的面向对象,都是基于组合的形式,主要是因为rust的设计理念不同。
Rust选择不使用继承来实现多态,而是使用trait和泛型来实现多态性。这是因为Rust的设计目标之一是提供内存安全和无运行时开销的抽象机制。
具体原因:
- 使用继承来实现多态性的语言,如C++和Java,通常需要在运行时进行动态分派,这需要额外的运行时开销。
- 此外,继承关系还引入了对象的大小和布局的问题。
Rust通过trait和泛型的组合提供了一种更安全、更灵活的多态性实现方式。
什么是Trait
trait是一种抽象机制,允许定义一组方法的契约,并让类型来实现这些方法。泛型允许在编写代码时引入抽象的类型参数,以便代码可以适用于不同的具体类型。这种静态的泛型多态性在编译时进行类型检查,并且没有运行时开销。 使用trait和泛型实现多态性的优势包括:
- 零运行时开销:Rust的trait和泛型在编译时进行静态分派,不需要额外的运行时开销。
- 内存安全:Rust的trait和泛型机制保证了类型安全和内存安全,避免了继承层次带来的一些问题,如对象切片的大小和布局问题。
- 更灵活的抽象:使用trait和泛型,可以根据需要定义和使用不同的行为集合,而不受固定的继承关系的限制。
综上,Rust选择使用trait和泛型来实现多态性,以提供更安全、更高效、更灵活的抽象机制,并符合Rust的设计目标和哲学。