29_rust_面向对象编程特性

面向对象编程特性

面向对象的特性

封装
rust默认情况下是私有的,使用pub关键字让方法和成员公开,访问也通过方法来实现。
继承
使对象可沿用另外一些对象的数据和行为。不过rust没有继承的概念,但rust通过默认trait方法来进行代码共享,也可使用同名方法覆盖原有实现。
多态
rust使用泛型和trait约束(限定参数化多态bounded parametric)实现了多态的概念。

例子:使用trait对象存储不同类型的值

需求:创建一个GUI 工具,会遍历某个元素的列表,依次调用元素的 draw 方法进行绘制,例如:Bufton、TextField 等元素。
如果在其他面向对象语言里,会定义一个 Component 父类,里面定义了 draw 方法,然后定义 Button、TextField 等类,继承与 Component 类。但rust语言无继承的功能,所以要采用其他方法。

为共有行为定义一个trait
rust避免将struct或enum称之为对象,因为它们与impl块分开的。
trait对象则有些类似于其它语言的对象,某种程度上组合了数据与行为。与传统对象不同的地方是无法为trait对象添加数据。
trait对象被专门用于抽象某些共有行为,没有其他语言的对象那么通用。
使用cargo new hello_cargo创建hello_cargo项目,
lib.rs的内容

pub trait Draw { // 公共trait,有一个draw方法
    fn draw(&self);
}

pub struct Screen { // 用于存放所有实现了Draw trait的实例,这样就可统一调用draw方法了
    pub comps: Vec<Box<dyn Draw>>, // 表示Box里元素都实现了Draw trait,只要实现了Draw trait的对象都可存入
}

impl Screen {
    pub fn run(&self) {
        for c in self.comps.iter() {
            c.draw();
        }
    }
}

/*******泛型实现版本*******/
// 如果使用泛型替代Box的实现,一次只能存放一种类型,无法实现类似多态的功能
// pub struct Screen<T: Draw> { // 泛型实现
//     pub comps: Vec<T>, // 如果T为Button类型,就只能存放Button这一种类型了,无法存放多种类型
// }

// impl<T> Screen<T>
// where
//     T: Draw,
// {
//     pub fn run(&self) {
//         for c in self.comps.iter() {
//             c.draw();
//         }
//     }
// }

pub struct Button {
    pub w: u32,
    pub h: u32,
    pub label: String,
}
impl Draw for Button {
    fn draw(&self) {
        println!("draw button: {},{},{}", self.label, self.w, self.h);
    }
}

main.rs的内容

use hello_cargo::Draw;
use hello_cargo::{Button, Screen};
struct SelBox {
    w: u32,
    h: u32,
    op: Vec<String>,
}
impl Draw for SelBox {
    fn draw(&self) {
        println!("draw select box: {:?}, {}, {}", self.op, self.w, self.h);
    }
}
fn main() {
    let sc = Screen {
        comps: vec![ // 可存放多种类型
            Box::new(SelBox {w: 1, h: 2, op: vec![String::from("t1"), String::from("t2")],}),
            Box::new(Button {w: 2, h: 3, label: String::from("b1"),}),
        ],
    };
    sc.run(); // 执行run可调用各自的draw方法
}
/*运行结果
draw select box: ["t1", "t2"], 1, 2
draw button: b1,2,3
*/

trait对象执行的是动态派发
将trait约束作用于泛型时,rust编译器会执行单态化,编译器会为用来替换泛型类型参数的每个具体类型,生成对应函数和方法的非泛型实现,简单理解就是用了哪种类型,就用哪种类型替换T。通过单态化生成的代码会执行静态派发(static dispatch),在编译过程中确定调用的具体方法。

动态派发(dynamic dispatch):无法在编译过程中确定调用的究竟是哪一种方法,编译器会产生额外的代码(虚表指针)以便在运行时动态寻找对应方法入口。使用trait对象,会执行动态派发,将产生一定的运行时开销,且无法内联优化等优化动作。

Trait对象必须保证对象安全,只能把满足对象安全(object-safe)的trait转化为tait对象,rust采用了一系列规则来判定某个对象是否安全,其中两条:

  • 方法的返回类型不是self
  • 方法中不包含任何泛型类型参数

面向对象的一种模式:状态模式

状态模式(state pattern):一个值拥有的内部状态由数个状态对象(state object)表达而成,而值的行为则随着内部状态的改变而改变,使用状态模式意味着业务需求变化时,不需要修改持有状态的值的代码,或者使用这个值的代码,只需要更新状态对象内部的代码,以便改变其规则。或者增加一些新的状态对象。
lib.rs内容:

pub struct Post {
    state: Option<Box<dyn State>>, // 三种状态,草稿、等待审批、审批通过
    content: String,
}
impl Post {
    pub fn new() -> Post { // 初始状态是Draft
        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 req_review(&mut self) {
        if let Some(s) = self.state.take() { // take()方法取出状态
            self.state = Some(s.req_review()) // 这里调用Box<Self>类型参数的函数,s就是一个Box
        }
    }
    pub fn approve(&mut self) {
        if let Some(s) = self.state.take() {
            self.state = Some(s.approve())
        }
    }
}

trait State {
    // Box<Self>类型参数作用是为了让只能是Box包裹的实例所调用,普通实例无法调用,且会在调用过程中获取Box<Self>所有权,并使旧的状态失效
    fn req_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 {}
impl State for Draft {
    fn req_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 req_review(self: Box<Self>) -> Box<dyn State> { self }
    fn approve(self: Box<Self>) -> Box<dyn State> { Box::new(Publish {}) }
}

struct Publish {}
impl State for Publish {
    fn req_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 }
}

main.rs

use hello_cargo::Post;

fn main() {
    // 草稿 审批 发布
    let mut p = Post::new();
    p.add_text("draft text");
    assert_eq!("", p.content());
    p.req_review();
    assert_eq!("", p.content());
    p.approve();
    assert_eq!("draft text", p.content());
}

由此可见状态模式的一些缺点,一是某些状态之间是相互耦合的,另一是需要重复实现一些逻辑代码。用rust实现,可使用rust语言的特征,将状态和行为编码为不同类型,rust类型检查系统会通过编译时错误阻止用户使用无效状态,以减少重复代码。
lib.rs内容

pub struct Post {
    content: String,
}

pub struct DraftPost {
    content: String,
}

pub struct PendingReviewPost {
    content: String,
}

impl Post {
    pub fn new() -> DraftPost {
        DraftPost { content: String::new(), }
    }
    pub fn content(&self) -> &str {
        &self.content // 只有发布之后的才有content方法获取内容
    }
}

impl DraftPost {
    pub fn add_text(&mut self, text: &str) {
        self.content.push_str(text);
    }
    pub fn req_review(self) -> PendingReviewPost{
        PendingReviewPost { content: self.content, }
    }
}

impl PendingReviewPost {
    pub fn approve(self) -> Post {
        Post { content: self.content, }
    }
}

main.rs内容

use hello_cargo::Post;

fn main() {
    // 草稿 审批 发布
    let mut p = Post::new();
    p.add_text("draft text");
    let p = p.req_review();
    let p = p.approve();
    assert_eq!("draft text", p.content());
}

可见rust了通过所有权等特性实现其他面向对象语言无法实现的代码。

posted @ 2023-11-21 01:05  00lab  阅读(17)  评论(0编辑  收藏  举报