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>