29_rust_面向对象编程特性

发布时间 2023-11-21 01:08:29作者: 00lab

面向对象编程特性

面向对象的特性

封装
rust默认情况下是私有的,使用pub关键字让方法和成员公开,访问也通过方法来实现。
继承
使对象可沿用另外一些对象的数据和行为。不过rust没有继承的概念,但rust通过默认trait方法来进行代码共享,也可使用同名方法覆盖原有实现。
多态
rust使用泛型和trait约束(限定参数化多态bounded parametric)实现了多态的概念。

例子:使用trait对象存储不同类型的值

需求:创建一个GUI 工具,会遍历某个元素的列表,依次调用元素的 draw 方法进行绘制,例如:Bufton、TextField 等元素。
如果在其他面向对象语言里,会定义一个 Component 父类,里面定义了 draw 方法,然后定义 Button、TextField 等类,继承与 Component 类。但rust语言无继承的功能,所以要采用其他方法。

为共有行为定义一个trait
rust避免将struct或enum称之为对象,因为它们与impl块分开的。
trait对象则有些类似于其它语言的对象,某种程度上组合了数据与行为。与传统对象不同的地方是无法为trait对象添加数据。
trait对象被专门用于抽象某些共有行为,没有其他语言的对象那么通用。
使用cargo new hello_cargo创建hello_cargo项目,
lib.rs的内容

pub trait Draw { // 公共trait,有一个draw方法
    fn draw(&self);
}

pub struct Screen { // 用于存放所有实现了Draw trait的实例,这样就可统一调用draw方法了
    pub comps: Vec<Box<dyn Draw>>, // 表示Box里元素都实现了Draw trait,只要实现了Draw trait的对象都可存入
}

impl Screen {
    pub fn run(&self) {
        for c in self.comps.iter() {
            c.draw();
        }
    }
}

/*******泛型实现版本*******/
// 如果使用泛型替代Box的实现,一次只能存放一种类型,无法实现类似多态的功能
// pub struct Screen<T: Draw> { // 泛型实现
//     pub comps: Vec<T>, // 如果T为Button类型,就只能存放Button这一种类型了,无法存放多种类型
// }

// impl<T> Screen<T>
// where
//     T: Draw,
// {
//     pub fn run(&self) {
//         for c in self.comps.iter() {
//             c.draw();
//         }
//     }
// }

pub struct Button {
    pub w: u32,
    pub h: u32,
    pub label: String,
}
impl Draw for Button {
    fn draw(&self) {
        println!("draw button: {},{},{}", self.label, self.w, self.h);
    }
}

main.rs的内容

use hello_cargo::Draw;
use hello_cargo::{Button, Screen};
struct SelBox {
    w: u32,
    h: u32,
    op: Vec<String>,
}
impl Draw for SelBox {
    fn draw(&self) {
        println!("draw select box: {:?}, {}, {}", self.op, self.w, self.h);
    }
}
fn main() {
    let sc = Screen {
        comps: vec![ // 可存放多种类型
            Box::new(SelBox {w: 1, h: 2, op: vec![String::from("t1"), String::from("t2")],}),
            Box::new(Button {w: 2, h: 3, label: String::from("b1"),}),
        ],
    };
    sc.run(); // 执行run可调用各自的draw方法
}
/*运行结果
draw select box: ["t1", "t2"], 1, 2
draw button: b1,2,3
*/

trait对象执行的是动态派发
将trait约束作用于泛型时,rust编译器会执行单态化,编译器会为用来替换泛型类型参数的每个具体类型,生成对应函数和方法的非泛型实现,简单理解就是用了哪种类型,就用哪种类型替换T。通过单态化生成的代码会执行静态派发(static dispatch),在编译过程中确定调用的具体方法。

动态派发(dynamic dispatch):无法在编译过程中确定调用的究竟是哪一种方法,编译器会产生额外的代码(虚表指针)以便在运行时动态寻找对应方法入口。使用trait对象,会执行动态派发,将产生一定的运行时开销,且无法内联优化等优化动作。

Trait对象必须保证对象安全,只能把满足对象安全(object-safe)的trait转化为tait对象,rust采用了一系列规则来判定某个对象是否安全,其中两条:

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