RUST Object-Oriented Programming
Rust 程序设计语言 - Rust 程序设计语言 简体中文版 (kaisery.github.io)
面向对象编程
Object-oriented programs are made up of objects. An object packages both data and the procedures that operate on that data. The procedures are typically called methods or operations.
Rust中的面向对象
rust中的struct和enum可以定义不同的数据结构,并可以给结构定义的方法
封装隐藏实现
rust中使用pub
关键字来控制数据结构访问,例如定义一个计算平均值的结构体,数据成员为私有,添加和删除方法为公开的,每次添加新的数据时自动调用计算平均值私有方法计算出平均值。
1 | pub struct AveragedCollection { |
当外部程序使用这个结构体时,不需要知道其中数据是怎么组织的,只需要调用添加、删除和平均值三个公开接口。如果这个结构内部数据结构调整或更新计算平均值的规则,外部使用者不会被影响。
类型继承实现代码复用
rust中的struct不支持父子继承关系,如果一定要复用接口,可以通过trait的方法默认实现,让struct声明支持一个trait的方法,这个方法在trait中已经提供了默认实现。
继承在现在很多编程语言中已经不是主流的编程范式,因为继承共享了太多不需要的实现,有的语言只支持单继承。但是我现在主要开发工作中面相对象还是最主要的编程方法,抽象,多态使用的还是很多的。
Trait Object
一个trait object同时指向一个实现了某个具体trait的实例和一个在运行时用来查找类型中trait方法的表格。trait object的声明需要一个指针如&引用
或Box<T>
并在trait类型前加上dyn
关键字。Trait object作为泛型或具体类型使用。rust编译器会保证对应的实例实现了trait的方法。
例如 Box<dyn Draw>
就是一个trait object,它表示在一个Box中的实现了Draw这个trait的任意类型。
下面的例子中假设gui库中有个Draw Trait,gui库中有个screen结构体,它的run方法调用每一个控件的draw方法。库默认提供了button控件。使用gui库的应用程序中可以自己定义一个SelectBox控件,它实现了Draw Trait,所以即使它并没有在库中定义,也可以加在screen的控件列表中被执行。
1 | pub trait Draw {// 定义一个有draw方法的trait |
与模版差异
对于上面的screen的例子如果使用模版来实现
1 | pub struct Screen<T: Draw> { |
- 模板每次只能具体化一个类型,例如
Screen<Button>
那么其中的控件就只能全部都是button,对于trait object就可以支持不同的类型。 - 对于模板,编译器在编译期就可以为每个类型生成对应的静态代码,而trait object是动态派发,rust在运行时通过trait object的方法指针来决定调用的方法,就存在方法查找的损耗,同时静态编译的方法中内联优化也无法在动态派发中支持,所以使用trait object的性能会差异性,但是更灵活。
rust实现面向对象设计模式
状态模式
状态模式在状态内部封装数据,数据或行为会根据状态而不同。每一个状态只处理自己支持的行为和如何切换到其他状态。状态对象的拥有者不需要知道状态如何切换。当业务发生变化时,只需要更新状态内部的代码或增加新的状态,而不用更改拥有状态的业务代码。
一个博客文章分为草稿、审阅、发布几个阶段,每个阶段有自己可以支持的操作,不同的阶段之间可以转换。
- 一个博客文章Post有内容和当前的状态
- Post默认为空的草稿状态
- Post添加内容后,直到发布前外部看到都是空内容,所以通过状态来确定Post的Content是什么
使用到的技术要点:
- 使用new方法来创建对象,并进行基本的初始化
- state trait的方法使用
Box<Self>
作为参数,并返回一个trait objectBox<dyn State>
- post使用state来处理返回的content时,把post作为引用传入方法,但是返回值又是post的成员,需要使用生命周期注解说明返回值的生命周期和入参post的生命周期相关
1 | pub struct Post { |
利弊
优点:
- 方便扩展新的状态,例如增加一个驳回操作,或者需要两次审阅才能发布
- 不需要很多的match分支判断
缺点:
- 状态之间存在依赖,一个状态切换下一个状态的规则
- 状态实现了公共接口Trait重复的代码
- Post需要委派相同的方法给state,例如 approve 方法
状态和行为定义为类型
除了使用面相对象的方式实现一个功能,还可以利用rust语言的特有机制实现相同的功能,面相对象不是唯一的方案。
rust编译器的类型检查可以帮助我们检查一个对象支持哪些操作,例如草稿状态下不能返回内容,只能进行审阅。
rust的所有权转移可以通过方法调用让一个类型转换为另一个类型的对象,例如:
- Post默认new出来的是DraftPost对象
- DraftPost对象有添加内容方法和请求审阅方法,请求审阅方法会返回一个PendingReviewPost对象
- PendingReviewPost对象执行它特有的approve方法,返回一个Post对象
1 | pub struct Post { |