Rust的面向对象(四)——为结构体或枚举实现特性
从1973年C语言主体完成到1983年C++正式命名,10年的时间,编程理念经历了一次从面向过程到面向对象的蜕变。数据的组织形式也从struct到class得到了升华。自那以后,几乎所有语言的类都是class,相当一部分程序员知道怎么用class却不能准确说出来面向对象是什么。然而,Go和Rust却同时站出来说:“我们不需要class。”顿时,全场静音。
事实上,面向对象的概念早在1967年的Simula语言中就开始提出来了,比C语言的面世还早了6年。可是时至今日,全网也找不到一个权威的面向对象的定义,有的都是用会意的方式来解释什么是面向对象。
相比面向对象抽象的定义而言,面向对象的三要素却是非常具体的,它们是:
- 封装:为了增强安全性和简化编程的复杂性,隐藏对象的属性和实现细节,仅对外公开接口
- 继承:为了提高代码复用率,子类可继承父类的属性和方法
- 多态:为了消除类型之间的耦合关系,提升代码的灵活性,子类可覆盖父类的方法
封装很好理解,就是定义类的属性和方法,并且提供访问级别来控制其它类的对象是否可以访问它们。Rust虽然没有类,但可以使用结构体或枚举来定义属性,并为结构体或枚举附加方法来实现。
而继承是类之间的关系,因为没有类,Rust和Go都没有继承的概念。不过Rust巧妙的用泛型以及泛型的特性实现了一套与继承相类似的代码共享机制。
多态是建立在继承之上的,它允许在使用父类的地方使用子类,Rust的特性也拥有类似的能力。
在学习Rust的面向对象之前,需要充分意识到接下来要学习的东西与你之前认识的世界有着天壤之别,不要被过去的知识所束缚,退一步,进入一个崭新的世界。
学习Rust的面向对象是一个漫长的过程,里面要掌握的知识非常多,我们可以使用下面的路线图来学习Rust的面向对象:
为结构体或枚举实现特性
强调一点,Rust是没有继承的,而编写代码的过程中,又经常会遇到许多不相同的类型有共同方法的情况,为了应对这种情况,Rust使用了特性这个概念。特性是一组方法,用来定义某些事物共有的行为。
过去我们学习面向对象时,总是用各种动物都会走路作为例子,先定义一个动物类,类中定义一个走路方法,然后派生出各种动物。在Rust中,就应该这么考虑,动物有一些共同的特性如走路、如鸣叫等。
定义特性
那么如何定义特性呢?这个很简单:
// 定义动物特性
trait Animal {
// 走路,在定义特性时直接实现
fn walk(&self) {
println!("闲庭信步");
}
// 奔跑,在定义特性时直接实现
fn run(&self) {
println!("来去如风");
}
// 鸣叫,类似于抽象方法,不在特性中实现,需要在实现特性时实现
fn sing(&self);
}
使用trait关键字定义Animal特性包括三个方法:
- walk方法,在特性中实现
- run方法,在特性中实现
- sing方法,不在特性中实现
为结构体实现特性
// 定义蜗牛结构体
struct Snail {
house: f32 // 房子半径,cm
}
// 为蜗牛实现动物特性
impl Animal for Snail {
// 重写run方法
fn run(&self) {
self.walk()
}
// 实现sing方法
fn sing(&self) {
println!("我不会叫唉。");
}
}
为结构体实现特性与实现方法类似,都使用impl关键字,不同的是impl后接的不是结构体的名字,而是特性的名字,然后用for关键字指定结构体的名字。
在impl块中,可以实现在特性中已经实现的方法,调用时impl中定义的方法生效。必须要实现特性中没有实现的方法,如果不实现将无法通过编译。
调用结构体的特性方法
实例化结构体后,就可以像调用方法那样调用特性中的方法:
fn main() {
let snail = Snail{house: 0.5};
snail.walk(); // 调用特性中的walk方法
snail.run(); // 调用Snail实现的run方法
snail.sing(); // 调用Snail实现的sing方法
}
在程序执行时walk方法调用的特性中的方法,run和sing方法调用的都是结构体中实现的方法。
为多个结构体实现同一特性
特性诞生的初衷就是为了代码共享,因为,可以同时为多个结构体或枚举实现同一特性:
// 定义动物特性
trait Animal {
// 走路,在定义特性时直接实现
fn walk(&self) {
println!("闲庭信步");
}
// 奔跑,在定义特性时直接实现
fn run(&self) {
println!("来去如风");
}
// 鸣叫,类似于抽象方法,不在特性中实现,需要在实现特性时实现
fn sing(&self);
}
// 定义蜗牛结构体
struct Snail {
house: f32 // 房子半径,cm
}
// 为蜗牛实现动物特性
impl Animal for Snail {
// 重写run方法
fn run(&self) {
self.walk()
}
// 实现sing方法
fn sing(&self) {
println!("我不会叫唉。");
}
}
// 定义公牛结构体,还记得吗,没有属性的结构体,被称为类单元结构体
struct Bill {}
// 为蜗牛实现动物特性
impl Animal for Bill {
// 实现sing方法
fn sing(&self) {
println!("哞~~");
}
}
fn main() {
let snail = Snail{house: 0.5};
snail.walk(); // 调用特性中的walk方法
snail.run(); // 调用Snail实现的run方法
snail.sing(); // 调用Snail实现的sing方法
let bill = Bill{};
bill.walk(); // 调用特性中的walk方法
bill.run(); // 调用特性中的run方法
bill.sing(); // 调用Bill实现的sing方法
}