Rust是面向对象编程语言吗?
Rust受到多种编程范式的影响,包括面向对象
面向对象通常包含以下特性:命名对象、封装、继承
1、对象包含数据和行为
“设计模式四人帮”在《设计模式》中给面向对象的定义:(设计模式学习之设计模式概述)
面向对象的程序由对象组成。
对象包装了数据和操作这些数据的过程,这些过程通常被称作方法或操作
基于此定义:Rust是面向对象的
struct、enum包含数据
impl块为之提供了方法
但带有方法的struct、enum并没有被称为对象
封装
封装:调用对象外部的代码无法直接访问对象内部的实现细节,唯一可以与对象进行交互的方法就是通过它公开的API
Rust: pub 关键字
pub struct AveragedCollection {
list:Vec<i32>,
average:f64,
}
impl AveragedCollection {
pub fn add(&mut self,value:i32) {
self.list.push(value);
self.update_average();
}
pub fn remove(&mut self)->Option<i32> {
let result = self.list.pop();
match result {
Some(value) => {
self.update_average();
Some(value)
},
None => None,
}
}
pub fn average(&self)->f64 {
self.average
}
fn update_average(&mut self) {
let total:i32 = self.list.iter().sum();
self.average = total as f64 / self.list.len() as f64;
}
}
继承
继承:使对象可以沿用另外一个对象的数据和行为,且无需重复定义相关代码
Rust:没有继承使用继承的原因:-代码复用
。Rust:默认trait方法来进行代码共享―多态
。 Rust:泛型和 trait约束(限定参数化多态 bounded parametric )
很多新语言都不使用继承作为内置的程序设计方案了。
2、使用trait对象来存储不同类型的值
为共有行为定义一个trait
Rust避免将struct或enum称为对象,因为它们与impl块是分开的
trait对象有些类似于其它语言中的对象:
它们某种程度上组合了数据与行为
trait对象与传统对象不同的地方:
无法为trait对象添加数据
trait对象被专门用于抽象某些共有行为,它没其它语言中的对象那么通用
pub trait Draw {
fn draw(&self);
}
pub struct Screen{
pub components:Vec<Box<dyn Draw>>,
}
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
用上述的代码中我们只要实现了Draw这个trait的对象就可以使用draw这个方法,这样我们就实现了代码的复用
当然我们也可以使用泛型来实现,但是使用泛型有一个问题,就是当泛型参数类型确定了,就不能在更改了
pub trait Draw {
fn draw(&self);
}
pub struct Screen<T>{
pub components:Vec<T>,
}
impl<T> Screen<T>
where T:Draw
{
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
完整的代码lib.rs:
pub trait Draw {
fn draw(&self);
}
pub struct Screen{
pub components:Vec<Box<dyn Draw>>,
}
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
pub struct Button {
pub width:i32,
pub height:i32,
pub label:String
}
impl Draw for Button {
fn draw(&self) {
// 绘制一个按钮
}
}
main.rs:
use my_thread::{Button, Draw, Screen};
struct SelectBox {
width:u32,
height:u32,
options:Vec<String>,
}
impl Draw for SelectBox {
fn draw(&self) {
// 绘制一个选择框
}
}
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:75,
height:10,
label:String::from("Ok")
})
]
};
screen.run();
}
Trait对象执行的是动态派发
将trait约束作用于泛型时,Rust编译器会执行单态化:
编译器会为我们用来替换泛型类型参数的每一个具体类型生成对应函数和方法的非泛型实现。
通过单态化生成的代码会执行静态派发(static dispatch),在编译过程中确定调用的具体方法
动态派发(dynamic dispatch) :
–无法在编译过程中确定你调用的究竟是哪一种方法
一编译器会产生额外的代码以便在运行时找出希望调用的方法
使用trait对象,会执行动态派发:
-产生运行时开销
一阻止编译器内联方法代码,使得部分优化操作无法进行
Trait对象必须保证对象安全
只能把满足对象安全(object-safe)的trait 转化为trait对象
Rust采用一系列规则来判定某个对象是否安全,只需记住两条:
方法的返回类型不是Self
方法中不包含任何泛型类型参数
3、实现面向对象的设计模式
状态模式
状态模式(state pattern)是一种面向对象设计模式:
一个值拥有的内部状态由数个状态对象(state object)表达而成,而值的行为则随着内部状态的改变而改变
使用状态模式意味着:
业务需求变化时,不需要修改持有状态的值的代码,或者使用这个值的代码
只需要更新状态对象内部的代码,以便改变其规则。或者增加一些新的状态对象
我们直接看代码:
lib.rs:
pub struct Post {
pub state: Option<Box<dyn State>>,
pub content:String
}
impl Post {
pub fn new()->Post {
Post {
state:Some(Box::new(Draft{})),
content:String::new(),
}
}
pub fn add_post(&mut self,text:&str) {
self.content.push_str(text);
}
pub fn content(&self) -> &str {
""
}
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>;
}
struct Draft {}
impl State for Draft {
fn request_review(self:Box<Self>) -> Box<dyn State> {
Box::new(PendingReview{})
}
fn approve(self:Box<Self>) -> Box<dyn State> {
self
}
}
impl State for Post {
fn request_review(self:Box<Self>) -> Box<dyn State> {
Box::new(PendingReview{})
}
fn approve(self:Box<Self>) -> Box<dyn State> {
self
}
}
struct PendingReview{}
impl State for PendingReview {
fn request_review(self:Box<Self>) -> Box<dyn State> {
Box::new(PendingReview{})
}
fn approve(self:Box<Self>) -> Box<dyn State> {
self
}
}
struct Published{}
impl State for Published {
fn approve(self:Box<Self>) -> Box<dyn State> {
Box::new(Published{})
}
fn request_review(self:Box<Self>) -> Box<dyn State> {
self
}
}
main.rs:
use state_demo::Post;
fn main() {
let mut post = Post::new();
post.add_post("I ate a salad for lunch today");
// assert_eq!("",post.content);
post.request_review();
// assert_eq!("",post.content);
post.approve();
// assert_eq!("I ate a salad for lunch today",post.content);
}
状态模式的取舍权衡
缺点:
―某些状态之间是相互耦合的
―需要重复实现一些逻辑代码
将状态和行为编码为类型
将状态编码为不同的类型:
- Rust类型检查系统会通过编译时错误来阻止用户使用无效的状态
Rust不仅能够实现面向对象的设计模式,还可以支持更多的模式
例如:将状态和行为编码为类型
面向对象的经典模式并不总是Rust编程实践中的最佳选择,因为Rust具有所有权等其它面向对象语言没有的特性!