Rust 的面向对象特性(OOP)
-
对象包含数据和行为
从这一点来看,结构体和枚举包含数据而 impl 块提供了在结构体和枚举之上的方法。虽然带有方法的结构体和枚举并不被称为对象,但是他们提供了与对象相同的功能。
-
封装隐藏了实现细节
封装使得改变和重构对象的内部时无需改变使用对象的代码。如果封装是一个语言被认为是面向对象语言所必要的方面的话,那么 Rust 满足这个要求。在代码中不同的部分使用 pub 与否可以封装其实现细节。
-
继承,作为类型系统与代码共享
选择继承有两个主要的原因:
- 是为了重用代码;
- 多态,这意味着如果多种对象共享特定的属性,则可以相互替代使用。
如果一个语言必须有继承才能被称为面向对象语言的话,那么 Rust 就不是面向对象的,不过Rust 也提供了其他的解决方案。
- Rust 代码可以使用默认 trait 方法实现来进行共享;
- 使用 trait 对象替代继承。泛型类型参数一次只能替代一个具体类型,而 trait 对象则允许在运行时替代多种具体类型
通用行为的 trait
-
trait 对象
的作用是允许对通用行为的抽象; -
泛型类型参数一次只能替代一个具体类型,而 trait 对象则允许在运行时替代多种具体类型。
pub struct Screen<T: Draw> { pub components: Vec<T>, } impl<T> Screen<T> where T: Draw { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } } pub trait Draw { fn draw(&self); } pub struct Button { pub width: u32, pub height: u32, pub label: String, } impl Draw for Button { fn draw(&self) { // 实际绘制按钮的代码 } } struct SelectBox { width: u32, height: u32, options: Vec<String>, } impl Draw for SelectBox { fn draw(&self) { // code to actually draw a select box } }
fn main() { let screen = Screen { components: vec![ Box::new(SelectBox { width: 75, height: 10, options: vec![ String::from("Yes"), String::from("Maybe"), String::from("No") ], }), Box::new(Button { width: 50, height: 10, label: String::from("OK"), }), ], }; screen.run(); }
-
当使用 trait 对象时,Rust 必须使用动态分发(静态分发发生于编译器在编译时就知晓调用了什么方法的时候, 与动态分发相对,编译器在编译时无法知晓调用了什么方法)
-
Trait 对象要求对象安全。在实践中,只涉及到两条规则:
- 返回值类型不为 Self
- 方法没有任何泛型类型参数
面向对象设计模式的实现–状态模式
- 状态模式是一个面向对象设计模式;
- 状态对象共享功能:在 Rust 中使用结构体和 trait 而不是对象和继承