18.1 面向对象语言的特征
18.1.1 对象:数据 + 行为
18.1.2 封装隐藏了实现细节
- 在 Rust 中,在代码中不同的部分考虑使用
pub
可以封装其实现细节
18.1.3 继承,作为类型系统与代码共享
- 在 Rust 中,不存在继承的机制,而是使用 trait 对象来提供相关的功能
18.2 trait 对象
- 已知全部的数据类型时,可以使用固定集合(枚举);但当全部的数据类型不是已知时(会动态变化),就需要使用 trait 对象来实现了
18.2.1 trait 对象(是一个实例):定义通用行为
trait
对象:指向一个实现了指定trait
类型的实例,以及一个用于在运行时查找该类型的trait
方法的表- 通过指定某种**指针(
&
引用或Box<T>
智能指针,还有dyn
关键字)**来创建trait
对象 - 可以使用
trait
对象代替泛型或具体类型,无需在编译时就知晓所有可能的类型 trait
对象将数据和行为两者相结合(不同于传统的对象,不能向trait
对象增加数据),trait
对象的作用是允许对通用行为进行抽象- 对比泛型类型:泛型类型参数一次只能替代一个具体类型,而
trait
对象则允许在运行时替代多种具体类型
18.2.2 实现 trait
- 将一个或多个类型的实例,放入
Box<T>
中就可以转换得到trait
对象! - 使用
trait
对象和 Rust 类型系统来进行类似鸭子类型操作的优势是无需在运行时检查一个值是否实现了特定方法或者担心在调用时因为值没有实现方法而产生错误。如果值没有实现trait
对象所需的trait
则 Rust 不会编译这些代码
18.2.3 trait 对象执行动态分发
18.2.4 trait 对象需要类型安全
- 只有**对象安全(object-safe)**的 trait 可以实现为特征对象
- 如果一个 trait 中定义的所有方法都符合以下规则,则该 trait 是对象安全的:1、返回值不是 Self;2、没有泛型类型的参数
- 核心原因:一旦使用 trait 对象,Rust 将不再知晓该实现的返回类型!
18.3 面向对象设计模式的实现
18.3.1 状态模式
- 状态模式(state pattern):是一个面向对象设计模式,该模式的关键在于定义一系列值的内含状态,这些状态体现为一系列的状态对象,同时值的行为随着其内部状态而改变
- 状态对象共享功能
- 每一个状态对象负责其自身的行为,以及该状态何时应当转移至另一个状态。而持有一个状态对象的值,对于不同状态的行为以及何时状态转移毫不知情
18.3.2 博客项目(一):定义 Post 并新建一个草案状态的实例
18.3.3 博客项目(二):存放博文内容的文本
- 对于需要修改的字段需要传入可变引用
&mut self
18.3.4 博客项目(三):确保博文草案的内容是空的
18.3.5 博客项目(四):请求审核博文来改变其状态
- 代码解析:
if let Some(s) = self.state.take()
表示先取出当前state
的值及其所有权,再给到中间变量s
,s
再根据实际获取到的state
值调用对应的request_review
方法(即判断是属于Draft
还是PendingReview
的request_review
) - 在这里来看看状态模式的优势:无论 state 是何值,Post 的 request_review 方法都是一样的。每个状态只负责它自己的规则
18.3.6 博客项目(五):增加改变 content 行为的 approve 方法
- 调用
Option
的as_ref
方法是因为需要Option
中值的引用而不是获取其所有权 - 在下面的示例中,获取到的
&Box<dyn State>
,当调用其 content 时,Deref 强制转换会作用于&
和Box
- 在下面的示例中,获取 post 的引用作为参数,并返回 post 一部分的引用,所以返回的引用的生命周期与 post 参数相关
18.3.7 状态模式的权衡取舍
- 状态模式的优点:Post 的方法和使用 Post 的位置无需
match
语句,同时增加新状态只涉及到增加一个新struct
和为其实现trait
的方法 - 状态模式的缺点:因为状态实现了状态之间的转换,一些状态会相互联系;会有一些重复的逻辑(可通过默认方法、定义宏等方法来解决)
18.3.8 博客项目-改造(一):将状态和行为编码为类型
18.3.9 博客项目-改造(二):实现状态转移为不同类型的转换