Rust的面向对象(五)——面向对象

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的面向对象:

  1. 创建结构体枚举
  2. 为结构体或枚举实现方法
  3. 为结构体或枚举实现特性

面向对象

通过前面的学习,我意识到Rust面向对象的与众不同,从面向对象的抽象定义讲,Rust是面向对象的,从面向对象的三要素来讲,Rust又不是面向对象的:Rust没有继承,而它又用另外的方法实现了类似继承的代码共享。因此,不能用传统的面向对象的思维来理解Rust。

最后,一起总结一下Rust面向对象的三要素。

封装

前面学习的为结构体或枚举实现方法,就是封装的体现。但这并不全面,封装不仅仅是把方法聚合在一起这么简单。还记不记得C++的封装还有很多信息保护或隐藏的手段:公有、私有、保护甚至友元,哪些类能访问其他类的哪些方法,是学习面向对象最头疼的问题之一。

Rust也同样拥有信息保护或隐藏的手段,但远没有C++那么复杂。它只有两种公有和私有,但只有一个关键字:pub。添加了pub关键字的方法是公有方法,没有添加的就是私有方法。回顾一下之前学习方法时的例子:

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());
}

Rect有两个方法,area和perimeter都没有pub关键字,那么,它们都是私有的,main之所以可以调用,是因为它们在同一个文件中。如果拆成两个文件,就不能用了。

rect.rs:

struct Rect(i64, i64);

impl Rect {
    fn area(&self) -> i64 {
        self.0 * self.1
    }

    fn perimeter(&self) -> i64 {
        (self.0 + self.1) * 2
    }
}

main.rs:

mod rect;   // 导入rect模块

fn main() {
    let rect1 = rect::Rect(20, 30);

    println!("area: {}", rect1.area());
    println!("perimeter: {}", rect1.perimeter());
}

编译就会出现问题:

error[E0603]: tuple struct constructor `Rect` is private
 --> src\main.rs:4:23
  |
4 |     let rect1 = rect::Rect(20, 30);
  |                       ^^^^ private tuple struct constructor
  |
 ::: src\rect.rs:1:13
  |
1 | struct Rect(i64, i64);
  |             -------- a constructor is private if any of the fields is private
  |
note: the tuple struct constructor `Rect` is defined here
 --> src\rect.rs:1:1
  |
1 | struct Rect(i64, i64);
  | ^^^^^^^^^^^^^^^^^^^^^^

error[E0624]: associated function `area` is private
 --> src\main.rs:6:32
  |
6 |     println!("area: {}", rect1.area());
  |                                ^^^^ private associated function

error[E0624]: associated function `perimeter` is private
 --> src\main.rs:7:37
  |
7 |     println!("perimeter: {}", rect1.perimeter());
  |                                     ^^^^^^^^^ private associated function

error: aborting due to 3 previous errors

问题一共有三个,第一个问题是说Rect结构体的定义是私有的,这也是Rust的特点,如果结构体要被外部使用,也需要定义成公有。第二个和第三个问题是相同的,结构体的方法也需要定义成公有,因此,将rect.rs改为:

pub struct Rect(i64, i64);

impl Rect {
    pub fn area(&self) -> i64 {
        self.0 * self.1
    }

    pub fn perimeter(&self) -> i64 {
        (self.0 + self.1) * 2
    }
}

这回该没问题了吧,然并卵:

error[E0603]: tuple struct constructor `Rect` is private
 --> src\main.rs:4:23
  |
4 |     let rect1 = rect::Rect(20, 30);
  |                       ^^^^ private tuple struct constructor
  |
 ::: src\rect.rs:1:17
  |
1 | pub struct Rect(i64, i64);
  |                 -------- a constructor is private if any of the fields is private
  |
note: the tuple struct constructor `Rect` is defined here
 --> src\rect.rs:1:1
  |
1 | pub struct Rect(i64, i64);
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

编译器又说,Rect的构造器使用的属性是私有的,需要指定为公有:

pub struct Rect(pub i64, pub i64);

impl Rect {
    pub fn area(&self) -> i64 {
        self.0 * self.1
    }

    pub fn perimeter(&self) -> i64 {
        (self.0 + self.1) * 2
    }
}

再编译,终于成功了。

继承与多态(特性)

姑且还是使用继承作为标题吧,但Rust是没有继承的,要共享代码,需要使用特性。

多态是对于继承而言的,Rust的多态,也是通过特性来实现的。

一切皆文件是一种linux的文化,无论是真正的文件,还是网络操作,都被看作是文件,因此,我们设计一个linux系统,首先要有一个类文件特性,然后在真正的文件和网络上实现它:

// 定义类文件特性
trait FileSimilar {
    fn read(&self) -> String;
    fn write(&mut self, content: &str);
}

// 定义文件结构体
struct File {
    path: String,
    content: String
}

// 实现类文件特性
impl FileSimilar for File {
    fn read(&self) -> String {
        self.content.clone()
    }

    fn write(&mut self, content: &str) {
        self.content += content;
    }
}

// 定义网络结构体
struct NetWork {
    url: String,
    content: String
}

// 实现类文件特性
impl FileSimilar for NetWork {
    fn read(&self) -> String {
        self.content.clone()
    }

    fn write(&mut self, content: &str) {
        self.content = String::from(content);
    }
}

fn main() {
    let mut f = File{path: String::from("d:\\test"), content: String::from("")};
    f.write("test");
    println!("{}", f.read());

    let mut nw = NetWork{url: String::from("127.0.0.1:80"), content: String::from("")};
    nw.write("test");
    println!("{}", f.read());
}

这个例子中,File和NetWork都实现了read和write方法,在调用时,自然而然的分别调用各自的实现,不会造成混乱。

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值