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的面向对象:
为结构体或枚举实现方法
在学习C++的时候,我们把一般的函数称为函数,类的函数我们叫它成员函数。学习Java的时候,我们把函数叫做方法。搞的我都想和老师吵架,到底是函数还是方法。后来函数和方法就一直被混着使用,直到学习了Rust。
Rust在概念上区分了函数和方法,不属于结构体的叫函数,属于结构体的叫方法。这里多提一句,还有一个叫特性(trait),是用于泛型的,上面用到的#[derive(Debug)]就是。
方法
为结构体定义方法先看下面的例子:
struct Rect(i64, i64);
impl Rect {
fn area(&self) -> i64 {
self.0 * self.1
}
fn perimeter(&self) -> i64 {
(self.0 + self.1) * 2
}
}
fn main() {
let rect1 = Rect(20, 30);
println!("area: {}", rect1.area());
println!("perimeter: {}", rect1.perimeter());
}
impl是implementation的缩写,后面加结构体名,表示为该结构体实现方法。要实现的方法包在impl块中,依然使用fn关键字。像python那样方法的第一个参数是结构体自身引用,因为是在impl块中,它不需要指定参数类型,虽然是参数名,但只能使用self,不能使用this等其他的词。
关联函数
此外,Rust还有一个概念叫做关联函数(associated functions),它类似于C++的静态函数,不过没有static关键字,只要参数里没有self的引用,那么这个函数就是关联函数,关联函数要通过结构体的名字空间进行访问,如:
#[derive(Debug)]
struct Rect(i64, i64);
impl Rect {
fn area(&self) -> i64 {
self.0 * self.1
}
fn perimeter(&self) -> i64 {
(self.0 + self.1) * 2
}
fn square(size: i64) -> Rect {
Rect(size, size)
}
}
fn main() {
let rect1 = Rect(20, 30);
println!("area: {}", rect1.area());
println!("perimeter: {}", rect1.perimeter());
println!("square: {:?}", Rect::square(10))
}
为枚举实现方法
Rust不仅可以为结构体实现方法,也可以为枚举实现,实现的方式与结构体相同:
enum WeekDay {
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
}
impl WeekDay {
fn show_mood(&self) {
match self {
WeekDay::Monday => println!("还没休息过来呢,又要开始上班了。"),
WeekDay::Tuesday => println!("昨天光抱怨了,今天必须努力工作。"),
WeekDay::Wednesday => println!("还好昨天效率高,耽误的工作补回来了。"),
WeekDay::Thursday => println!("这周过去一半多了,胜利就在前方。"),
WeekDay::Friday => println!("为了明天不加班,今天还要再努力。"),
WeekDay::Saturday => println!("终于可以休息了,吃什么好吃的呢。"),
WeekDay::Sunday => println!("在家憋了一天,今天出去逛逛吧。")
}
}
}
fn main() {
let day = WeekDay::Sunday;
day.show_mood();
}
多个impl块
一个impl块中,可以包含多个方法,事实中,多个方法也可以被包含在多个impl块中,甚至你可以把这些impl块分别写在不同的.rs文件中。
struct Rect(i64, i64);
impl Rect {
fn area(&self) -> i64 {
self.0 * self.1
}
}
impl Rect {
fn perimeter(&self) -> i64 {
(self.0 + self.1) * 2
}
}
fn main() {
let rect1 = Rect(20, 30);
println!("area: {}", rect1.area());
println!("perimeter: {}", rect1.perimeter());
}