《Rust权威指南》 第17章 Rust面向对象特性

面向对象的语言特性

对象包含数据和行为

这个我们通过结构体和枚举类型,以及他们所对应的impl块实现

封装

使用我们之前说的,包、crate、模块,以及pub关键字实现封装

继承

Rust没有继承机制,且许多新潮的语言也都抛弃了继承的机制
继承有两个被使用的原因

  • 代码复用。让子类型复用父类型的代码。这里给出的替代方案是trait
  • 多态,Rust中多态的实现方式是trait对象

使用trait对象储存不同的值

我们知道,在C++中,多态的一大特征是父类的引用或指针可以指向子类的实例

共有行为trait以及trait对象

trait对象能指向实现了指定trait的类型实例
选用一种指针实现:关键字dyn和trait名

  • 正常引用:&dyn MyTrait
  • Box智能指针:Box<dyn MyTrait>

例子:UI库
一个常见的情形,在UI库中,每个控件有自己的draw方法来显示在屏幕上。
我们将这一共有行为抽象为trait

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

创建一个Screen结构,其由一个构件动态数组组成。在这里我们使用trait对象使得该数组中可以存储不同的控件类型

pub struct Screen {
    pub components:Vec<Box<dyn Draw>>,
}

之后我们就可以在方法的实现中统一的调用trait包含的方法了

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

trait对象会执行动态派发

静态派发

  • 比如泛型代码单态化执行的是静态派发,程序在编译期间就确定了调用何方法

动态派发

  • 由于我们不知道trait对象储存的具体类型,所以只能执行动态派发
  • 在运行时,去根据trait对象内部的指针找到具体执行哪个版本的方法。这会又一些运行时的开销

正如C++一模一样的
使用 dyn 关键字创建 trait 对象时,编译器会为每个 trait 对象生成一个虚函数表(vtable)。虚函数表是一个包含函数指针的数据结构,用于存储 trait 中定义的方法的地址。每个 trait 对象会持有一个指向其对应虚函数表的指针。

当调用 trait 对象上的方法时,编译器会通过虚函数表查找正确的方法地址并进行调用。这使得在运行时可以根据实际对象的类型动态地进行方法调用。

trait对象必须保证对象安全

满足以下两条规则的trait是对象安全的

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

这是因为就算在运行时,我们也无法得知trait对象存储的具体类型。

例子:实现一种面向对象的设计模式——状态模式

状态模式是使得对象在其内部状态改变时改变其行为
实现这个特征的一般方法是在实现对象时,让对象内部包含一个状态变量,并在编写对象的方法时使用分支语句判断对象当前的状态
更好的方法是,实现一个状态类,组合到对象去。让不同的状态对象拥有具体行为,真正对象只需要调用对应的方法

这里给出的例子是一个博客工作流程:
一篇博客有三个状态:草稿、审批中、已提交。
草稿可以通过审批变成审批中,审批中可以经由通过变成已提交
只有已提交状态下可以查看正文内容

pub struct Post {
    state:Option<Box<dyn State>>,
    content:String,
}

impl Post {
    pub fn new() -> Post {
        Post { state: Some(Box::new(Draft {})), content: String::new() }
    }

    pub fn add_text(&mut self,text:&str) {
        self.content.push_str(text);
    }

    pub fn content(&self) -> &str {
        self.state.as_ref().unwrap().content(&self)
    }

    pub fn request_review(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.request_review())
        }
    }
    pub fn approve(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.approve())
        }
    }
}

trait State {
    fn request_review(self:Box<Self>) -> Box<dyn State> ;
    fn approve(self:Box<Self>) -> Box<dyn State>;
    fn content<'a>(&self,post:&'a Post) -> &'a str {
        ""
    }
}

struct Draft {}
struct Pending {}
struct Published {}

impl State for Draft {
    fn request_review(self:Box<Self>) -> Box<dyn State> {
        Box::new(Pending {})
    }
    fn approve(self:Box<Self>) -> Box<dyn State> {
        self
    }
}

impl State for Pending {
    fn request_review(self:Box<Self>) -> Box<dyn State> {
        self
    }
    fn approve(self:Box<Self>) -> Box<dyn State> {
        Box::new(Published {})
    }
}

impl State for Published {
    fn request_review(self:Box<Self>) -> Box<dyn State> {
        self
    }
    fn approve(self:Box<Self>) -> Box<dyn State> {
        self
    }
    fn content<'a>(&self,post:&'a Post) -> &'a str {
        &(post.content)
    }
}

有几点注意:

  • 作为“状态变量”的结构体,不包含数据,只是实现了State这一trait
  • 有关Option<T>的两个方法
    • .state().take():取出Option<T>包含的Some值的所有权,并在原地留下一个None
    • as_ref:接受Option<T>而获得引用版本Option<&T>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值