参考章节《Rust程序设计语言》第17.1章 面向对象语言的特征
我们通常认为一门编程语言是否支持面向对象特性一般是看它是否支持封装
,继承
,多态
,这也就是面向对象的三大特性
那么我们来看看Rust是否支持面向对象
- 面向对象中的对象
面向对象的程序是由对象组成的。一个对象
包含数据
和操作这些数据的过程
。这些过程通常被称为 方法
或 操作
。
在这个定义下,Rust 是面向对象的:结构体和枚举包含数据
而 impl
块提供了在结构体和枚举之上的方法
。
虽然带有方法的结构体和枚举并不被称为对象
,但是他们确实提供了与对象相同的功能
- 面向对象中的封装
封装的思想:对象的实现细节
不能被 使用对象的代码获取到
。
如果封装是一个语言被认为是面向对象语言所必要的方面的话,那么 Rust 满足这个要求。在代码中不同的部分使用 pub
与否可以封装其实现细节。
- 面向对象中的继承
继承是一个很多编程语言都提供的机制,一个对象可以定义为继承另一个对象的定义,这使其可以获得父对象的数据和行为,而无需重新定义。
如果一个语言必须有继承才能被称为面向对象语言的话,那么 Rust 就不是面向对象的。因为无法定义一个结构体继承父结构体的成员和方法。
1
|
在 Rust 中我们可以使用带有 默认实现 的 trait 来实现继承的效果,当实现这个 trait 时也可以选择 覆盖 这个方法的 默认实现。这类似于子类覆盖从父类继承的方法实现。
|
- 面向对象中的多态
多态
表现为子类型可以用于父类型被使用的地方。这意味着如果多种对象共享特定的属性,则可以相互替代使用。
1
2
|
在 Rust 中我们仍然可以通过 trait 来实现多态的效果,我们可以定义一个trait对象指针,可以使用 & 引用或 Box<T> 智能指针
它指向一个实现了我们指定 trait 的类型的实例,这样无论是不是不同的类型,都可以当作同一个类型对待,也就实现了多态的效果
|
我们来看一个书上的例子
假如我们有一个接口,它有一个 draw
方法,用于绘制一个东西
1
2
3
|
pub trait Draw {
fn draw(&self);
}
|
我们有一个屏幕,屏幕上可以放很多组件,而且它们都必须实现Draw trait
1
2
3
|
pub struct Screen<T: Draw> {
pub components: Vec<T>, // 存放组件
}
|
我们现在实现它,当调用 run
方法时,会调用屏幕上每个组件的 draw
方法
我们想想这么做有什么问题?
1
2
3
4
5
6
7
8
9
10
|
impl<T> Screen<T>
where
T: Draw,
{
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
|
假如现在我们有两个组件,它们都实现了 draw
方法
一个按钮
1
2
3
4
5
6
7
8
9
10
11
|
pub struct Button {
pub width: u32,
pub height: u32,
pub label: String,
}
impl Draw for Button {
fn draw(&self) {
println!("Button draw.")
}
}
|
一个选择框
1
2
3
4
5
6
7
8
9
10
11
|
struct SelectBox {
pub width: u32,
pub height: u32,
pub options: Vec<String>,
}
impl Draw for SelectBox {
fn draw(&self) {
println!("SelectBox draw.")
}
}
|
我们来使用它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
fn main() {
let screen = Screen {
components: vec![
SelectBox {
width: 75,
height: 10,
options: vec![
String::from("Yes"),
String::from("Maybe"),
String::from("No"),
],
},
Button {
width: 50,
height: 10,
label: String::from("OK"),
},
],
};
screen.run();
}
|
运行这段代码,你会发现这段代码并不允许编译,原因是因为类型不匹配
1
2
3
|
error[E0308]: mismatched types
--省略部分报错--
expected struct `SelectBox`, found struct `Button`
|
你会发现,这根本无法满足多态的要求,虽然 Button
和 SelectBox
都实现了 Draw trait
但它们两个还是无法当作同一个类型对待,这时候就我们就可以利用指针
来代替具体类型
了
trait对象指针
我们来看看之前的定义 Vec<T>
,这个 T
就代表一个固定的类型,因此你无法传递其他类型
1
2
3
|
pub struct Screen<T: Draw> {
pub components: Vec<T>, // 存放组件
}
|
所以我们要让他能够支持不同的类型,我们可以使用Box<T>
智能指针,如下所示,它代表任何实现了 Draw trait
的类型的替身,
1
2
3
|
pub struct Screen {
pub components: Vec<Box<dyn Draw>>,
}
|
然后 run
方法的实现也要改一下
1
2
3
4
5
6
7
|
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
|
最后我们来看看修改后的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
fn main() {
let screen = Screen {
components: vec![
Box::new(SelectBox {
width: 75,
height: 10,
options: vec![
String::from("Yes"),
String::from("Maybe"),
String::from("No"),
],
}),
Box::new(Button {
width: 50,
height: 10,
label: String::from("OK"),
}),
],
};
screen.run();
}
|
运行这段代码
1
2
3
4
5
6
|
Blocking waiting for file lock on build directory
Compiling rust_test v0.1.0 (/home/w/data/code/rust_test)
Finished dev [unoptimized + debuginfo] target(s) in 0.28s
Running `/home/w/data/code/rust_test/target/debug/rust_test`
SelectBox draw.
Button draw.
|
可以看到,这一次就可以了,我们成功向 components
中放入了不同类型的组件,但它们必须都实现了 Draw trait
通过这种方法,我们就可以实现类似面向对象中的多态的效果
总结一下
- 对于面向对象编程 Rust 为我们提供了另一种解决方案
- 至于 Rust 是否是一门面向对象的编程语言就仁者见仁智者见智了,我认为是