写点什么

30 天拿下 Rust 之面向对象

作者:希望睿智
  • 2024-06-02
    安徽
  • 本文字数:2630 字

    阅读完需:约 9 分钟

30天拿下Rust之面向对象

💡 如果想阅读最新的文章,或者有技术问题需要交流和沟通,可搜索并关注微信公众号“希望睿智”。

概述

在编程语言的世界中,Rust 以其独特的内存安全、并发控制和高性能特性吸引了众多开发者。虽然 Rust 并非传统的面向对象编程语言(比如:C++、Java),但它依然支持并提供了一种颇具特色的面向对象编程方式,以实现类似于面向对象的编程范式。

在 Rust 中,没有类的概念,但提供了模块、结构体、枚举、Trait 来模拟面向对象编程的三大特性:封装、继承和多态。下面,我们分别进行介绍。

封装

Rust 的封装机制提供了一种强大的方式来隐藏实现细节,仅暴露必要的接口给使用者。封装是面向对象编程的三大基本特性之一,它有助于创建模块化的代码,提高代码的可维护性和安全性。在 Rust 中,封装主要通过模块、结构体、私有性等特性来实现。

Rust 使用模块来组织代码,每个模块都有自己的作用域,可以包含函数、类型定义、其他模块等。模块提供了一种自然的封装方式,可以将相关的代码组织在一起,并通过 pub 关键字来控制哪些内容对外部可见。在之前的专栏文章中,我们曾专门介绍过模块,故这里就不再进一步展开了。

在 Rust 中,结构体的字段可以通过指定访问修饰符来控制其可见性。默认情况下,字段是私有的,这意味着它们只能在定义结构体的模块内部被访问。通过 pub 关键字,我们可以将字段设置为公有,以便在外部访问。

我们在下面的 my_module.rs 文件中声明了公开的函数 public_func 和公开的结构体 PublicStruct。private_func 函数由于没有使用 pub 关键字,默认为私有的。

// my_module.rspub fn public_func() {    // ...}
fn private_func() { // ...}
pub struct PublicStruct { pub public_field: i32,}
复制代码

接着,我们在下面的 main.rs 文件中使用了模块 my_module 中的函数和结构体。由于 private_func 是私有的,因此,无法使用 use 关键字导入,编译会提示错误:function `private_func` is private。

// main.rsmod my_module;
use my_module::PublicStruct;use my_module::public_func;// 错误:该函数是私有的,无法使用use my_module::private_func;
fn main() { let data = PublicStruct { public_field: 66 }; println!("{}", data.public_field); public_func();}
复制代码

继承

Rust 并没有传统意义上的继承机制,更倾向于使用组合和 Trait 来复用和扩展代码。然而,通过一些模式,我们可以在 Rust 中实现类似继承的效果。

使用组合模拟继承

可以通过将一个类型作为另一个类型的字段来实现组合,这可以模拟继承中子类包含父类字段的效果。

struct Parent {    value: i32,}
impl Parent { fn do_something(&self) { println!("parent data: {}", self.value); }}
struct Child { parent: Parent, extra_value: String,}
impl Child { fn new(value: i32, extra_field: String) -> Child { Child { parent: Parent { value }, extra_value: extra_field, } }
fn do_child_thing(&self) { println!("child data: {}", self.extra_value); }
// 委托给父类的方法 fn do_something(&self) { self.parent.do_something(); }}
fn main() { let child = Child::new(66, "World".to_string()); // 调用父类的方法 child.do_something(); // 调用子类的方法 child.do_child_thing();}
复制代码

在上面的示例代码中,Child 结构体包含一个 Parent 类型的字段 parent。这允许 Child 访问和调用 Parent 的方法,从而模拟了继承的行为。同时,Child 还可以添加自己的字段和方法。

使用 Trait 模拟接口继承

Trait 在 Rust 中类似于接口,它们定义了一组方法签名,可以由不同的类型来实现,这可以模拟接口继承的效果。

trait Animal {    fn speak(&self);}
struct Dog { name: String,}
impl Animal for Dog { fn speak(&self) { println!("dog {} speak", self.name); }}
struct Cat { name: String,}
impl Animal for Cat { fn speak(&self) { println!("cat {} speak", self.name); }}
fn animal_speak(animal: &dyn Animal) { animal.speak();}
fn main() { let dog = Dog { name: "Buddy".to_string() }; let cat = Cat { name: "Whiskers".to_string() };
animal_speak(&dog); animal_speak(&cat);}
复制代码

在上面的示例代码中,我们定义了一个 Animal 特征,它有一个 speak 方法。Dog 和 Cat 结构体都实现了 Animal 特征,因此它们都可以被视为动物,并且具有 speak 方法。通过动态分发(使用 &dyn Animal),我们可以编写接受任何实现了 Animal 特征的类型的函数,比如这里的 animal_speak。

多态

在 Rust 中,多态通常是通过 Trait 和泛型来实现的。多态允许我们编写灵活的代码,这些代码可以处理多种不同的类型,只要这些类型满足某些共同的接口或约束。Trait 定义了类型必须实现的方法集合,从而允许我们编写与这些类型交互的通用代码。在上面介绍继承的示例代码中,我们已经看到了基于 Trait 的多态实现,故这里就不再赘述了。

接下来,我们使用泛型来实现多态。泛型允许我们编写可以处理多种类型的函数或结构体,而不需要在编译时指定具体的类型。

fn find_max<T: Ord>(slice: &[T]) -> &T {    let mut max = &slice[0];    for item in slice.iter() {        if item > max {            max = item;        }    }    max}
fn main() { let numbers = vec![66, 99, 100, 50]; let max_number = *find_max(&numbers); println!("{}", max_number);
let fruits = vec!["Lemon", "Apple", "Date"]; let max_fruit = find_max(&fruits); println!("{}", max_fruit);}
复制代码

在上面的示例代码中,我们定义了一个泛型函数 find_max,它接受一个实现了 Ord 特征(即可以排序的类型)的切片,并返回其中的最大值。由于 Ord 特征是由多种标准库类型实现的,我们可以使用这个函数来找出整数切片中的最大值,或字符串切片中基于字典序的最大字符串。

总结

虽然 Rust 并不是传统意义上的面向对象编程语言,但它提供了丰富的工具来模拟和实现面向对象的概念。通过结构体与方法的组合、Trait 与接口的定义、泛型的使用,Rust 可以让我们以面向对象的方式来组织和封装代码,实现高内聚、低耦合的代码结构。正是这种灵活性,使得 Rust 能够适应各种复杂的编程需求,成为系统级编程的理想选择。

发布于: 刚刚阅读数: 4
用户头像

希望睿智

关注

一起学习,一起成长,一起进步! 2024-05-21 加入

中国科学技术大学毕业,在客户端、运营级平台、Web开发、嵌入式开发、深度学习、人工智能、音视频编解码、图像处理、流媒体等多个领域具备实战开发经验和技术积累,共发表发明专利十余项,软件著作权几十项。

评论

发布
暂无评论
30天拿下Rust之面向对象_面向对象_希望睿智_InfoQ写作社区