当前位置: 首页 > article >正文

Rust面向对象特性

Rust的面向对象特性

本文已同步至自建博客

Rust在设计的时候受到很多编程范式的影响,包括面向对象。面向对象的语言共有一些共同的特征,即对象、封装和继承。

封装

一个对象的实现细节对使用该对象的代码不可访问。因此,对象交互的唯一方式是通过其公共 API;使用对象的代码不应能直接触及对象的内部并改变数据或行为。这使得程序员能够更改和重构一个对象的内部实现,而无需改变使用该对象的代码。

使用pub 关键字来控制封装,Rust语言使用 pub 关键字来决定代码中的哪些模块、类型、函数和方法是公有的,而默认情况下其他所有内容都是私有的。

示例:

pub struct AveragedCollection {
    // 集合
    list: Vec<i32>,
    // 平均数
    average: f64,
}

impl AveragedCollection {
    pub fn add(&mut self, value: i32) {
        self.list.push(value);
        self.update_average();
    }

    pub fn remove(&mut self) -> Option<i32> {
        let result = self.list.pop();
        match result {
            Some(value) => {
                self.update_average();
                Some(value)
            }
            None => None,
        }
    }

    pub fn average(&self) -> f64 {
        self.average
    }

    fn update_average(&mut self) {
        let total: i32 = self.list.iter().sum();
        self.average = total as f64 / self.list.len() as f64;
    }


}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        let mut c = AveragedCollection {
            list: Vec::new(),
            average: 0.0,
        };
        c.add(1);
        c.add(5);
        println!("average value: {}", c.average());
        c.remove();
        println!("remove a node, and now average value: {}", c.average());


    }

}

结构体AveragedCollection中有listaverage两个字段,因为没有pub 关键字修饰,默认是私有的,也就是在结构体外部不能直接访问到。通过定义由pub关键字修饰的方法,使得可以通过方法访问并操作结构体中的字段。示例中添加元素,和移除元素是公共的方法,更新平均数方法是私有的。因为不希望外部可以修改平均数,这就是封装。

继承

继承:使得对象可以沿用另外一个对象的数据或行为,且无需重复定义相关代码。

Rust 语言没有继承

使用继承的场景:

  1. 代码复用,Rust可以使用trait,默认的trait方法可以进行代码共享
  2. 多态:泛型和trait约束(限定参数化多态 bounded parametric)

为共有行为定义一个trait

创建一个GUI工具:

  1. 它会遍历某个元素的列表,依次调用元素的draw方法进行绘制,例如:Button,TextField等元素。

在面向对象语言中,惯例是定义一个父类Component,声明一个draw方法。定义Button,TextField 等类,继承Component类。

在Rust中需要trait来实现:

注意:

  1. Rust避免将struct和enum 称之为对象,因为它们与impl块是分开的。

  2. Trait 对象有些类似于其他语言的对象,因为在某种程度上组合了数据与行为。trait 对象与传统对象不同的地方:无法为trait对象添加数据。

  3. trait对象被专门用于抽象某些共有行为,它没有其他语言的对象那么通用。

类比Java 的接口(interface),接口定义方法声明,不同的实现类实现接口来提供不同的具体实现。比如一个Draw接口,定义了一个draw方法,不同的实现类实现Draw接口后,draw方法执行的内容不一样的。

这在Rust中通过 trait也可以实现同样的效果,示例如下:

同一个方法,不同结构体的方法可以进行不同的具体实现

示例代码:

pub trait Draw {
    fn draw(&self);
}

/// `Vec<Box<dyn Draw>>` 在这里的用法含义:无法再编译期确定单一类型,就要使用智能指针在堆上分配
pub struct Screen {
    pub components: Vec<Box<dyn Draw>>,
}

// 为结构体Screen 实现一个方法
impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

pub struct Button {
    pub width: u32,
    pub height: u32,
    pub label: String,
}

impl Draw for Button {
    fn draw(&self) {
        println!("Button drawing")
    }
}

pub struct SelectBox {
    pub width: u32,
    pub height: u32,
    pub options: Vec<String>,
}

impl Draw for SelectBox {
    fn draw(&self) {
        println!("SelectBox drawing")
    }
    
}
    #[test]
    fn test_trait_oop() {

        let screen = Screen {
            components: vec![
                Box::new(SelectBox {
                    width: 75,
                    height: 10,
                    options: vec![String::from("Yes"), String::from("No"),]
                }),
                Box::new(Button {
                    width: 50,
                    height: 10,
                    label: String::from("OK"),
                }),
            ]
        };
        screen.run();
    }

执行结果:

running 1 test
test tests::test_trait_oop ... ok

successes:

---- tests::test_trait_oop stdout ----
SelectBox drawing
Button drawing


successes:
    tests::test_trait_oop

示例代码中有 SelectBoxButton 两个结构体,同时为两个结构体都实现了Draw 这个trait。

通过 Screen 这个结构体的run方法执行传入trait对象的方法。pub components: Vec<Box<dyn Draw>> 这里使用Box表示这个数组中的对象是动态的。

我们在执行代码的时候,是动态像数组中填充对象的,这无法在编译期确定具体的对象类型。所以这里使用Box

当Button执行draw方法时,执行的是 Button 结构体中的draw方法实现,当SelectBox执行draw方法时,执行的是SelectBox中draw方法的实现。

Trait对象执行的是动作派发

将trait约束作用于泛型时,Rust编译期会执行单态化:

编译器会为我们用来替换泛型类型参数的每一个具体类型生成对应函数和方法的非泛型实现。通过单态化生成的代码会执行静态派发(static dispatch),在编译过程中确定调用的具体方法。

动态派发(dynamic dispach):无法在编译过程中确定调用的究竟是哪一个方法,编译器会产生额外的代码以便运行时找出希望调用的方法。

使用trait对象,会执行动态派发:

产生运行时开销,阻止编译器内联方法代码,使得部分优化操作无法进行

Trait对象必须保证对象安全

只能把满足对象安全(object-safe)的trait转化为trait对象。

Rust采用一系列规则来判定某个对象是否安全,只需要记住两条:

  1. 方法的返回不是self
  2. 方法中不包含任何泛型类型参数

http://www.kler.cn/a/422021.html

相关文章:

  • Spring Boot 3.4.0 发布:功能概览与示例
  • 开源C代码之路:一、Gitee
  • 【从零开始的LeetCode-算法】3274. 检查棋盘方格颜色是否相同
  • 【Spring】介绍一下 Spring 的 xml 标签以及 Bean 的常用配置
  • Python实现网站资源批量下载【可转成exe程序运行】
  • QT实战-qt各种菜单样式实现
  • 第三方Express 路由和路由中间件
  • 攻防世界-fileclude-文件包含
  • springboot 项目 层级架构
  • aisuite - 一个接口调用多个大模型
  • 大语言模型在研究领域的应用---下
  • MySQL、Oracle、SQL Server 和 PostgreSQL 的分页查询
  • Dxf2Map:跨平台 BIM、GIS、CAD 和 AR 应用程序
  • 故障诊断 | Transformer-GRU-Adaboost组合模型的故障诊断(Matlab)
  • 语言模型测试系列【11】
  • Springfox迁移到 Springdoc OpenAPI 3
  • 基于智能语音交互的智能呼叫中心工作机制
  • C语言标准的演进与应用:C89与C99的比较
  • yolo辅助我们健身锻炼
  • WPF软件花屏的解决方法
  • 基于云模型和遗传算法的建设工程风险决策多目标优化研究
  • (译)提示词工程指南:如何写出让AI更听话的提示词(Prompt)?| 附完整示例和小学生版本
  • 安装使用Ubuntu18.04超级大全集最初版(anaconda,pycharm,代理,c/c++环境)
  • 我的创作纪念日—128天的坚持|分享|成长
  • vue+uniapp+echarts的使用(H5环境下echarts)
  • JVM 之垃圾回收器